Вопросы о "философии" Rust

Здравствуйте!
Только начинаю учить Rust и появилось несколько вопросов о самих подходах в реализации языка.

  1. Я правильно понимаю, что в Rust нет указателей в смысле C, Java и даже php? Т.е. всё переменные хранят именно значения, размещаемые, как правило, в стеке. Если нужна именно ссылка - нужно использовать Box<>.

  2. Синтаксический сахар - его очень много? Скажем оператор ?, который работает исключительно с типом Result, с другим - не получится. Или стандартные трейты, например, Deref - ведь нельзя средствами языка реализовать прозрачное обращение к оборачиваемому значению (вызов методов и т.п.). Так это или нет?

Спасибо.

оператор ? с версии 1.22 можно использовать с Option<T>.

  1. Нет, неправильно. Есть и указатели, как безопасные, так и опасные в стиле Си, и ссылки, изменяемые и неизменяемые. Box предназначен для работы со значениями, размещаемыми в куче.
  2. “Сахара” столько, чтобы не страдать от низкоуровневости языка. Для “синтаксических диабетиков” ничего страшного, думаю, нет.

По первому вопросу. В Rust есть несколько видов того, что с точки зрения C/C++ называлось бы указателем. Это и Box (да, этот тип несколько магический, но по сути это указатель-владелец), и ссылки &/&mut (указатель-заимствующий), и сырые указатели *const/*mut (наиболее близки к пониманию указателей в терминах C, поскольку для них владение/заимствование нужно отслеживать вручную), и “умные указатели” Rc/Arc с подсчётом ссылок. Собирать их в одну кучу “указателей” обычно не имеет смысла.

Более того, отдельные библиотеки вполне могут добавлять новые типы “указателей” (например, атомарные указатели крейта crossbeam-epoch).

2 лайка

“Сахара” столько, чтобы не страдать от низкоуровневости языка. Для “синтаксических диабетиков” ничего страшного, думаю, нет.

Но то, что я привёл - это он или нет?

Синтаксический сахар по определению не привносит никаких новых возможностей. Всё, что делается с помощью него, можно сделать и без него.
Поэтому ? это “сахар”, а трэйт Deref и перегружаемая им операция разыменования (*) это не “сахар”, а возможность языка.

По второму. Судя по примерам, кажется, что речь идёт скорее не о “сахаре”, а о “магии”, когда компилятор знает конкретные типы/трейты, и воспроизвести это на других невозможно без патчинга компилятора. Такая магия реализуется в терминах lang items, полный список которых можно увидеть в документации. В него входят все “базовые” типы наподобие целых чисел разного размера и все типажи, описывающие применение конкретных операторов, кучка механики для реализации паники в терминах исключений, управление памятью, поддержка замыканий и ещё десятка полтора разной важной всячины, такой как Clone/Copy, Send/Sync, Drop и ещё некоторые.
Кроме того, существует некоторое количество типов и типажей, которые нестабильны и не могут использоваться на стабильной версии Rust, потому что их интерфейс ещё не окончательный и/или эта функциональность принципиально нестабильна.

С другой стороны, именно “сахара” - сокращённой записи для того, что и так можно записать более длинно - в языке не очень много и он, в основном, стандартный (скажем, синтаксис вызова методов формально тоже сахар, но обычно это никого не интересует).

3 лайка

Т.е. по сути, lang items это это возможность языка, просто реализованная в немного других терминах чтобы не вводить отдельное понятия?
В C++ перегрузка как-то явнее сделана.

lang items - это те детали реализации, про которые должне знать именно сам компилятор, существования типов в библиотеке недостаточно. По сути, это точки перехода между реализацией внутри компилятора и стандартными типами/трейтами, которые её поддерживают. В частности, сюда входит вся перегрузка операторов (компилятор должен знать, что a + b “расшифровывается” как std::ops::Add(a, b)). Есть в языке и вещи, которые такой явной поддержки не требуют, или которые считаются деталями реализации изначально и не попадают в этот список (например, ссылки).

Что касается перегрузки, то тут есть забавный семантический момент. В C++ перегрузка описывается в терминах операторов (“для моего типа << делает то-то”). В Rust она описывается в терминах поведения (“для моего типа “сдвиг влево” делается так-то”). Это не только отражает идею трейтов как единиц поведения типа, но и продвигает идею, что операторы должны перегружаться по назначению, с поведением, аналогичным оригинальному (а не плюсовое "<< - это сдвиг влево, за исключением кучки магических типов, для которых это вывод пользователю / добавление в коллекцию / ещё что-то совсем отдельное").

1 лайк

Всем спасибо!
Стало намного понятнее.

Выкладывал тут уже схему, которую скомпилировал для себя из разных источников общая картина по указателям и контейнерам в Rust
обновлённая версия.

2 лайка