Rust — новый язык программирования: часть 1. общее описание, характеристики и свойства

Содержание:

Предварительный обзор функциональных возможностей языка

Характеристики системы типов данных: , ,
, ,
(локально выводимые).

Безопасная работа с памятью обеспечивается отстутствием null-указателей или «висящих»
указателей, также исключено переполнение буферов.

Обобщённая настраиваемая параметризация типов с использованием классов типов.

Обработка исключений выполняется без восстановления состояния, без «обратной раскрутки» (unwinding), с изоляцией (обрабатываемой) задачи.

Модель памяти: необязательный к применению сборщик мусора (GC), локальный относительно одной задачи,
безопасные типы указателей с анализом их области видимости.

Компилятор и все вспомогательные инструментальные средства публикуются под двойной лицензией: MIT / Apache 2.

Other Syntax

  • : named lifetime or loop label. See Lifetimes, .
  • , , , , …: numeric literal of specific type.
  • : string literal. See Strings.
  • , , , …: raw string literal, escape characters are not processed. See .
  • : byte string literal, constructs a instead of a string. See .
  • , , , …: raw byte string literal, combination of raw and byte string literal. See .
  • : character literal. See .
  • : ASCII byte literal.
  • : closure. See Closures.
  • : path. See .
  • : path relative to the crate root (i.e. an explicitly absolute path). See .
  • : path relative to the current module (i.e. an explicitly relative path). See .
  • : path relative to the parent of the current module. See .
  • : associated constants, functions, and types. See Associated Types.
  • : associated item for a type which cannot be directly named (e.g. , , etc.). See Associated Types.
  • (e.g. ): specifies parameters to generic type in a type. See Generics.
  • , (e.g. ): specifies parameters to generic type, function, or method in an expression.
  • : define generic function. See Generics.
  • : define generic structure. See Generics.
  • : define generic enumeration. See Generics.
  • : define generic implementation.
  • : higher-ranked lifetime bounds.
  • (e.g. ): a generic type where one or more associated types have specific assignments. See Associated Types.
  • : generic parameter constrained to types that implement . See Traits.
  • : generic type must outlive lifetime .
  • : generic lifetime must outlive lifetime .
  • : allow generic type parameter to be a dynamically-sized type. See .
  • , : compound type constraint. See .
  • : outer attribute. See Attributes.
  • : inner attribute. See Attributes.
  • : macro substitution. See Macros.
  • : macro capture. See Macros.
  • : macro repetition. See Macros.
  • : line comment. See Comments.
  • : inner line doc comment. See Comments.
  • : outer line doc comment. See Comments.
  • : block comment. See Comments.
  • : inner block doc comment. See Comments.
  • : outer block doc comment. See Comments.
  • : empty tuple (a.k.a. unit), both literal and type.
  • : parenthesized expression.
  • : single-element tuple expression. See .
  • : single-element tuple type. See .
  • : tuple expression. See .
  • : tuple type. See .
  • : function call expression. Also used to initialize tuple s and tuple variants. See Functions.
  • , , : macro invocation. See Macros.
  • , , …: tuple indexing. See .
  • : block expression.
  • : literal. See Structs.
  • : array literal. See .
  • : array literal containing copies of . See .
  • : array type containing instances of . See .
  • : collection indexing. Overloadable (, ).
  • , , , : collection indexing pretending to be collection slicing, using , , , as the «index».

А также

Я не ставлю себе целью написать учебник по Rust, а просто хочу подчеркнуть его особенности. В этом разделе опишу, что еще есть полезного, но, на мой взгляд, не такого уникального:

  • Любители функционального программирования не будут разочарованы, для них есть лямбды. У итератора есть методы для обработки коллекции, например, filter и for_each. Чем-то похоже на стримы из Java.
  • Конструкция match так же может быть использована для более сложных вещей, чем обычные enum, например, для обработки паттернов
  • Есть большое количество встроенных классов, например, коллекций: Vec, LinkedList, HashMap и т.д.
  • Можно создавать макросы
  • Есть возможность добавлять методы в существующие классы
  • Поддерживается автоматическое выведение типов
  • Вместе с языком идет стандартный фреймворк для тестирования
  • Для сборки и управления зависимостями используется встроенная утилита cargo

История

Работа над языком была начата Грэйдоном Хором в 2006 году, в 2009 году к разработке подключилась Mozilla, и в 2010 году язык был официально представлен на Mozilla Summit 2010. Также в 2010 году разработка языка была переведена с использования компилятора, написанного на OCaml, на компилятор, написанный на Rust, с использованием LLVM в качестве back-end; в следующем году он успешно скомпилировал сам себя.

Альфа-версия (0.1) вышла в январе 2012 года. В апреле 2013 года, когда вышла версия 0.6, запущен Servo — проект браузерного движка Samsung на Rust, портированный на ARM.

Версия 1.0 выпущена в мае 2015 года, программные интерфейсы и возможности языка подверглись значительной ревизии, после которой по умолчанию оставлены только полностью готовые к применению возможности, реализация которых не будет изменяться в дальнейшем. Все остальные функции переведены в разряд экспериментальных и вынесены из поставки по умолчанию. В 2016—2020 годах в ежегодных опросах Stack Overflow Rust каждый раз занимает первое место в списке любимых языков (в 2015 году — третье место).

Keywords

  • : primitive casting. See .
  • : break out of loop. See .
  • : constant items and constant raw pointers. See and , Raw Pointers.
  • : continue to next loop iteration. See .
  • : external crate linkage. See .
  • : fallback for and constructs. See , .
  • : defining enumeration. See Enums.
  • : external crate, function, and variable linkage. See , Foreign Function Interface.
  • : boolean false literal. See .
  • : function definition and function pointer types. See Functions.
  • : iterator loop, part of trait syntax, and higher-ranked lifetime syntax. See , Method Syntax.
  • : conditional branching. See , .
  • : inherent and trait implementation blocks. See Method Syntax.
  • : part of loop syntax. See .
  • : variable binding. See Variable Bindings.
  • : unconditional, infinite loop. See .
  • : pattern matching. See Match.
  • : module declaration. See .
  • : part of closure syntax. See .
  • : denotes mutability in pointer types and pattern bindings. See Mutability.
  • : denotes public visibility in fields, blocks, and modules. See .
  • : by-reference binding. See .
  • : return from function. See .
  • : implementor type alias. See Traits.
  • : method subject. See .
  • : global variable. See .
  • : structure definition. See Structs.
  • : trait definition. See Traits.
  • : boolean true literal. See .
  • : type alias, and associated type definition. See Aliases, Associated Types.
  • : denotes unsafe code, functions, traits, and implementations. See Unsafe.
  • : import symbols into scope. See .
  • : type constraint clauses. See .
  • : conditional loop. See .

Обобщенные типы

Помимо того, что обобщенные типы избавляют от написания шаблонного кода, они являются отличным инструментом абстрагирования и высокоуровневой спецификации поведения. Вот что я имею ввиду:

Тут уже сразу по сигнатуре функции понятно, что мы можем принять в аргументах объекты одного и того же типа , для которого реализовано отношение частичного порядка. Таким образом, от указания конкретного типа, мы переходим к указанию неких характеристик используемого типа с помощью типажей (traits), которые могут быть присущи самым разным типам. Можно указывать и достаточно нетривиальные зависимости между типами. Однако сами по себе характеристики, ровно как и соответствующие им типажи, могут быть как высокоуровневыми, так и низкоуровневыми. То есть они могут характеризовать тип как со стороны требований решаемой задачи, так и со стороны требований реализации. Вот небольшой пример из стандартной библиотеки:

Метод хеш-таблицы, хранящей значения типа , соответствующие ключам типа , принимает ссылку некоторого типа , которую также можно получить из ссылки на ключ (это правило задано отношением ). Что это значит? Это значит, что для случая, например, строковых ключей () вместо того, чтобы всякий раз передавать в ссылку на владеющую строку (), вы можете, скажем, передавать строковый срез (), к которому можно привести ссылку на владеющую строку с помощью операции . Это позволяет избежать лишних аллокаций и просто повышает удобство использования: вызывающий код не должен думать, какую именно строку передавать — метод корректно сработает для обоих вариантов.

Выглядит как что-то низкоуровневое. Но такое поведение часто реализует определенное требование самой задачи. Например, у нас есть тип с приватным конструктором:

Можно сделать так, что будет возможно сконструировать только с помощью некоего сервиса, который либо выдает новое число из глобальной последовательности идентификаторов, либо десериализует значение из ранее сконструированного и сохраненного. (Подробнее о преимуществах подобного подхода к проектированию типов вы можете прочитать в статье Парсите, а не валидируйте.)

Теперь мы хотим использовать как ключ в хеш-таблице, и это хорошо, так как у нас в таком случае появляется гарантия, что значение когда-то было корректно сконструировано и добавлено в таблицу, других вариантов его получить нет. Но мы должны иметь возможность обращаться к значению также и по ключу типа ! Потому что принятое число, например от пользователя, может оказаться верным значением идентификатора, а может и нет, и мы не хотим всякий раз делать сложную проверку его корректности, нам достаточно того, можем мы получить значение из хеш-таблицы по этому ключу, или не можем. Такое поведение легко осуществить, если мы реализуем соответствующий :

Вернемся к определению метода . Для типа там представлено также одно весьма низкоуровневое ограничение — . Оно говорит о том, что тип не обязательно должен быть типом с известным размером на этапе компиляции (что задано по умолчанию для обобщенных типов). Так как в метод передается только ссылка на этот тип, сам тип может быть и типом с неизвестным размером. Размеры типов принимаемых и возвращаемых значений у функций всегда должны быть известны на этапе компиляции, иначе будет невозможно зарезервировать для них место на стеке.

Итак, на границе высокоуровневого и низкоуровневого кода, проходящей через определения обобщенных типов, мы можем столкнуться как с высокоуровневыми, так и с низкоуровневыми ограничениями, причем далеко не всегда просто отделить одни от других. Но помочь с этим может введение новых типажей:

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

Operators and Symbols

  • (, , ): denotes macro expansion. See Macros.
  • (): bitwise or logical complement. Overloadable ().
  • (): arithmetic remainder. Overloadable ().
  • (): arithmetic remainder & assignment.
  • (): bitwise and. Overloadable ().
  • (): borrow. See References and Borrowing.
  • (, , , ): borrowed pointer type. See References and Borrowing.
  • (): bitwise and & assignment.
  • (): logical and.
  • (): arithmetic multiplication. Overloadable ().
  • (): dereference.
  • (, ): raw pointer. See Raw Pointers.
  • (): arithmetic multiplication & assignment.
  • (): arithmetic addition. Overloadable ().
  • (, ): compound type constraint. See .
  • (): arithmetic addition & assignment.
  • : argument and element separator. See Attributes, Functions, Structs, Generics, Match, Closures, .
  • (): arithmetic subtraction. Overloadable ().
  • (): arithmetic negation. Overloadable ().
  • (): arithmetic subtraction & assignment.
  • (, ): function and closure return type. See Functions, Closures.
  • (, ): diverging function or closure. See .
  • (): member access. See Structs, Method Syntax.
  • (, , , ): right-exclusive range literal.
  • (): struct literal update syntax. See .
  • (, ): «and the rest» pattern binding. See .
  • (): inclusive range pattern. See .
  • (): arithmetic division. Overloadable ().
  • (): arithmetic division & assignment.
  • (, ): constraints. See Variable Bindings, Functions, Structs, Traits.
  • (): struct field initializer. See Structs.
  • (): loop label. See .
  • : statement and item terminator.
  • (): part of fixed-size array syntax. See .
  • (): left-shift. Overloadable ().
  • (): left-shift & assignment.
  • (): less-than comparison. Overloadable (, ).
  • (): less-than or equal-to comparison. Overloadable (, ).
  • (, ): assignment/equivalence. See Variable Bindings, Aliases, generic parameter defaults.
  • (): comparison. Overloadable (, ).
  • (): part of match arm syntax. See Match.
  • (): greater-than comparison. Overloadable (, ).
  • (): greater-than or equal-to comparison. Overloadable (, ).
  • (): right-shift. Overloadable ().
  • (): right-shift & assignment.
  • (): pattern binding. See .
  • (): bitwise exclusive or. Overloadable ().
  • (): bitwise exclusive or & assignment.
  • (): bitwise or. Overloadable ().
  • (): pattern alternatives. See .
  • (): closures. See Closures.
  • (): bitwise or & assignment.
  • (): logical or.
  • : «ignored» pattern binding. See .

Выражения и операторы

Rust — в первую очередь язык, ориентированный на выражения. Есть только два типа
операторов, а всё остальное является выражением.

А в чём же разница? Выражение возвращает значение, в то время как оператор —
нет. Вот почему мы получаем здесь «not all control paths return a value»:
оператор не возвращает значение. Есть два типа операторов в Rust:
«операторы объявления» и «операторы выражения». Все остальное — выражения.
Давайте сначала поговорим об операторах объявления.

Оператор объявления — это связывание. В некоторых языках связывание переменных
может быть записано как выражение, а не только как оператор. Например, в Ruby:

Однако, в Rust использование для связывания не является выражением.
Следующий код вызовет ошибку компиляции:

Здесь компилятор сообщил нам, что ожидал увидеть выражение, но является
оператором, а не выражением.

Обратите внимание, что присвоение уже связанной переменной (например: )
является выражением, но его значение не особенно полезно. В отличие от других
языков, где результатом присваивания является присваиваемое значение (например,
из предыдущего примера), в Rust значением присваивания является пустой
кортеж

Вторым типом операторов в Rust является оператор выражения. Его цель —
превратить любое выражение в оператор. В практическом плане, грамматика Rust
ожидает, что за операторами будут идти другие операторы. Это означает, что вы
используете точку с запятой для отделения выражений друг от друга. Rust выглядит
как многие другие языки, которые требуют использовать точку с запятой в конце
каждой строки. Вы увидите её в конце почти каждой строки кода на Rust.

Из-за чего мы говорим «почти»? Вы это уже видели в этом примере:

Наша функция объявлена как возвращающая . Но если в конце есть точка с
запятой, то вместо этого функция вернёт . Компилятор Rust обрабатывает эту
ситуацию и предлагает удалить точку с запятой.

Бесплатные абстракции

Удивительно, насколько наличие абстракций с нулевой стоимостью, даже самых элементарных, упрощает прикладную разработку. Посмотрите на следующий код, который написан на языке, считающимся высокоуровневым:

И сравните с тем, как то же самое поведение реализуется в Rust:

Где вам понятнее, что происходит и где, по-вашему, вероятность ошибиться меньше? Мне кажется, что ответ очевиден.

Помимо демонстрации отличного дизайна стандартной библиотеки, этот пример показывает, насколько Rust может быть удобен для высокоуровневого программирования: вместо того, чтобы принимать в свои функции непонятные числа, вы можете использовать собственные типы, созданные под конкретную задачу, и это без дополнительных накладных расходов. (Правда, в случае структуры с несколькими полями, как у , по-умолчанию будет производиться выравнивание, но это поведение можно менять с помощью атрибута .)

Числовой тип — это «низкоуровневый» тип, потому что он отвечает на вопрос как значение будет представлено в памяти, а не на вопрос что оно собой представляет в контексте задачи. Но в Rust можно очень легко и элегантно вводить новые типы поверх существующих:

Несмотря на то, что оба значения и имеют одинаковое числовое представление, они являются объектами разных типов, и поэтому перепутать и подставить одно значение вместо другого не получится. Этот паттерн называется «Новый тип» (New type), и он совершенно бесплатен в использовании. (Подробнее о преимуществах использования паттерна «Новый тип» вы можете прочитать в замечательной статье Передача намерений.)

«Новый тип», так же как и вообще любая пользовательская структура или перечисление в Rust, может выступать границей раздела нескольких уровней программирования. И чтобы пользователь мог переходить эту границу всегда, когда это удобно для решения его задачи, эти абстракции не должны сами по себе требовать сколь-либо значимых дополнительных расходов. Иначе пользователь будет вынужден чаще пользоваться имеющимися низкоуровневыми типами, вместо того, чтобы создавать на их основе свои, высокоуровневые.

Документирование исходного кода

Основной способ документирования проекта на Rust заключается в комментировании
исходного кода. Для этой цели вы можете использовать документирующие
комментарии:

Этот код генерирует документацию, которая выглядит . В приведенном
коде реализация метода была заменена на обычный комментарий

Первое, на что
следует обратить внимание в этом примере, это на использование вместо. Символы указывают, что это документирующий комментарий

Документирующие комментарии пишутся на Markdown.

Rust отслеживает такие комментарии, и использует их при создании документации.

При документировании таких вещей, как перечисления, нужно учитывать некоторые
особенности работы . Такой код работает:

А такой — нет:

Вы получите ошибку:

Эта досадная ошибка
заключается в следующем: комментарии документации распространяются на элементы,
расположенные за ними, а в данном примере нет элемента, расположенного после
последнего комментария.

Давайте рассмотрим каждую часть приведенного комментария в деталях:

Первая строка документирующего комментария должна представлять из себя краткую
информацию о функциональности. Одно предложение. Только самое основное.
Высокоуровневое.

Наш исходный пример включал только строку с краткой информацией, но если бы у
нас было больше информации, о которой следует сказать, мы могли бы добавить эту
информацию в новом параграфе.

Далее идут специальные разделы. Они обознаются заголовком, который начинается с
. Существуют три вида заголовков, которые обычно используются. Они не
являются каким-либо специальным синтаксисом, на данный момент это просто
соглашение.

Раздел . Неустранимые ошибки при неправильном вызове функции (так
называемые ошибки программирования) в Rust, как правило, вызывают панику,
которая, в крайнем случае, убивает весь текущий поток (thread). Если ваша
функция имеет подобное нетривиальное поведение — т.е

обнаруживает/вызывает
панику, то очень важно задокументировать это

Раздел

Если ваша функция или метод возвращает , то
хорошим тоном является описание условий, при которых она возвращает .
Это чуть менее важно, чем описание , потому как неудача кодируется в
системе типов, но это не значит, что стоит пренебрегать данной возможностью

Раздел . Если ваша функция является , необходимо пояснить, какие
инварианты вызова должны поддерживаться.

Раздел . Включите в этот раздел один или несколько примеров
использования функции или метода, и ваши пользователи будут вам благодарны.
Примеры должны размещаться внутри блоков кода, о которых мы сейчас поговорим.
Этот раздел может иметь более одного подраздела:

Давайте подробно обсудим блоки кода.

Чтобы написать код на Rust в комментарии, используйте символы «`:

Если вы хотите написать код на любом другом языке (не на Rust), вы можете
добавить аннотацию:

Это позволит использовать подсветку синтаксиса, соответствующую тому языку,
который был указан в аннотации. Если же это простой текст, то в аннотации
указывается .

Важно выбрать правильную аннотацию, потому что использует ее
интересным способом: Rust может выполнять проверку работоспособности примеров на
момент создания документации. Это позволяет избежать устаревания примеров.
Предположим, у вас есть код на C

Если вы опустите аннотацию, указывающую, что
это код на C, то будет думать, что это код на Rust, поэтому он
пожалуется при попытке создания документации.

unsafe-блоки

Давайте сразу обратимся к примеру и посмотрим, как в Rust работают итераторы:

Здесь мы создаем массив из трех элементов, затем получаем итератор на него и добавляем к нему два адаптера — и , после чего собираем итератор в новую коллекцию типа . Этот код — достаточно высокоуровневый и довольно типичный для многих высокоуровневых языков (таких как JavaScript). Но давайте теперь посмотрим, как реализован объект типа , который мы получаем в результате вызова метода :

Обратите внимание, что структура содержит в качестве своих полей два указателя: и (строки 2 и 3). Из-за того, что эти указатели — это обычные Си-совместимые указатели (правда дополнительно требует, чтобы указатель не был нулевым), довольно низкоуровневые ссылочные типы, их время жизни никак не отслеживается borrow checker’ом

Поэтому заданное в объявлении структуры время жизни ссылки (1) мы вынуждены добавить в «фантомное» поле с типом нулевой размерности (4). Иначе время жизни окажется никак не используемым внутри структуры, что приведет к ошибке компиляции. То есть, другими словами: мы хотим сделать безопасный итератор, который ссылается на элементы коллекции, по которой он итерируется, и для того, чтобы он был безопасным, нам нужно учитывать время жизни ссылок. Но наша внутренняя реализация основана на указателях и потому не подразумевает никакого отслеживания времен жизни со стороны компилятора. Поэтому мы должны гарантировать своей реализацией безопасность кода, работающего с указателями (в unsafe-блоках, подобных 5), и тогда можно реализовать безопасный внешний API по всем правилам работы в safe Rust.

Это очень наглядный пример того, что представляет собой Rust на самом деле. Это высокоуровневый, безопасный язык, в котором есть низкоуровневые небезопасные возможности. Тут граница, по которой одно отделяется от другого — это определение типа, а блоки unsafe выступают маркером того, что в реализации используются весьма низкоуровневые и небезопасные средства (на самом деле в общем случае граница проходит через определение модуля: пока в язык не будет добавлена возможность помечать поля как unsafe, потенциально небезопасным становится весь код в пределах модуля, если на поведение unsafe-методов влияет содержимое полей структуры).

Важный вывод, к которому мы здесь приходим, состоит в том, что Rust — самодостаточный язык. Его высокоуровневые возможности вполне реализуются на низком уровне им же самим. И наоборот: из низкоуровневых «кирпичей» в Rust можно конструировать высокоуровневые блоки, скрывая детали реализации за безопасным API.

Теперь должно быть понятно, что unsafe, который тут и там встречается в стандартной библиотеке Rust — это не баг, а фича. Есть довольно популярный упрек к Rust со стороны: дескать, какой же это безопасный и высокоуровневый язык, если у него в сплошные unsafe-блоки? Он либо тогда должен быть весь unsafe, либо полностью safe. Но преимущество Rust как раз состоит в том, что он позволяет делать и то и другое, при этом отделяя одно от другого. Это одна из причин, почему Rust по-настоящему универсальный язык программирования.

Объектная система

В Rust объектная система основана на типажах (traits) и структурах (structs). Типажи определяют сигнатуры методов, которые должны быть реализованы для каждого типа (чаще всего — структуры), реализующего типаж. Типаж может содержать и реализации методов, принимаемые по умолчанию. Реализация типажей для данной структуры, а также реализация собственных методов структуры обозначается ключевым словом . Язык содержит несколько десятков встроенных типажей, большая часть которых используется для перегрузки операторов, а некоторые имеют специальное значение.

Rust поддерживает аналогию наследования типажей — типаж может требовать от реализующего типа реализацию других типажей. Однако языковой поддержки наследования самих типов, и следовательно, классического ООП, в Rust нет. Вместо наследования типов, аналогия иерархии классов реализуется введением типажей, включением структуры-предка в структуру-потомка или введением перечислений для обобщения разных структур.

Язык поддерживает обобщённые типы (generics). Помимо функций, обобщёнными в Rust могут быть комплексные типы данных, структуры и перечисления. Компилятор Rust компилирует обобщённые функции весьма эффективно, применяя к ним мономорфизацию (генерация отдельной копии каждой обобщённой функции непосредственно в каждой точке её вызова). Таким образом, копия может быть адаптирована под конкретные типы аргументов, а следовательно, и оптимизирована для этих типов. В этом отношении обобщённые функции Rust сравнимы по производительности с шаблонами языка C++.

Выпуск Rust 1.41.1: корректирующий выпуск

Перевод

Команда Rust опубликовала новый корректирующий выпуск Rust, 1.41.1. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.

Если вы установили предыдущую версию Rust средствами , то для обновления до версии 1.41.1 вам достаточно выполнить следующую команду:

Если у вас ещё не установлен , вы можете установить его с соответствующей страницы нашего веб-сайта

Что вошло в стабильную версию 1.41.1

Rust 1.41.1 посвящён двум критическим регрессиям, появившимся в Rust 1.41.0: некорректность в статических временах жизни и неправильная компиляция, вызывающая ошибки сегментирования. Эти регрессии не затрагивают предыдущие выпуски Rust и мы рекомендуем пользователям Rust 1.41.0 обновиться настолько быстро, насколько это возможно. Другая проблема, связанная со взаимодействием между временем жизни и реализациями типажа , присутствовала ещё с Rust 1.0 и тоже исправляется данным выпуском.

Управление памятью

В языке реализована модель управления памятью, ориентированная на безопасные шаблоны параллельного выполнения, препятствующая некорректному доступу к памяти, который обычно является источником критических ошибок сегментации в других языках программирования. Обеспечивается контроль за использованием неинициализированных и деинициализированных переменных; невозможно совместное использование разделяемых состояний несколькими задачами; обеспечивается статический анализ времени жизни указателей и проверка на выход за пределы массива (автоматически и всегда, но доступно отключение проверки в блоках, с помощью метода ).

Реализована так называемая Move-семантика: по умолчанию Rust «переносит» (move) указатель на объект в куче новому владельцу при присваивании, делая старую переменную недействительной. Этого не происходит, если тип реализует типаж Copy, поскольку данные в стеке копируются.

leta="объект с данными в куче".to_string();
// объект передан переменной b
// переменная a становится неинициализированной
letb=a;
// ошибка!
letc=a;
// данные объекта на стеке                                                                                       
leta=55;
// копия объекта передана переменной b                                                                   
letb=a;
// c = 55                                                                                        
letc=a;

Ещё одна особенность модели памяти — поддержка заимствований (borrow) с возможностью изменения заимствованного объекта () и без таковой (): Лексически и семантически очень схожи со ссылками, но имеют специфику: заимствование объекта сходно семантике «Либо много читателей, либо один писатель» — объект можно передать в заимствование либо однократно с возможностью изменения объекта, либо многократно без таковой; заимствования можно перезаимствовать другому заемщику. В отличие от обычной семантики «Либо много читателей, либо один писатель», применяется не в контексте синхронизации потоков, а универсально. Контроль корректности заимствований происходит во время компиляции и не порождает дополнительного исполнимого кода (принцип абстракций с нулевой стоимостью). Компилятором контролируется также соотношение времён жизни заимствований и самого объекта — заимствования не могут жить дольше (выходить за пределы области видимости) заимствованного объекта. Заимствования работают с любыми данными независимо от их размещения (стек, локальная или обменная куча, другие специальные расположения). Следует различать независимые понятия — изменяемость собственно заимствования () и изменяемость заимствованного объекта ().

Упаковка (Box) — «умный» указатель, владеющий объектом в куче, уничтожает объект и освобождает память при выходе из области видимости.

Ячейка (Cell, RefCell) реализует изменяемость содержимого при неизменяемости самой ячейки.

Указатели со счётчиком ссылок () и с атомарным счётчиком ссылок (): Умные указатели с подсчётом ссылок, уничтожающие объект и освобождающие память при обнулении счётчика. Arc реализует потокобезопасность для счётчика ссылок (но не для самого объекта). Rc и Arc контролируют неизменяемый объект, поэтому типичное их использование выглядит как в однопоточной программе и в многопоточной.

«Сырые» указатели неизменяемые () и изменяемые (): Указатели без гарантии безопасности. Настоятельно не рекомендуется их использовать.

Связывания неизменяемы по умолчанию, а чтобы объявить переменную изменяемой, необходимо ключевое слово mut.

Примеры:

letx=80;// связывание владельца x со значением 80
letmuty=50;// изменяемое связывание
letz=&x;// неизменяемая ссылка на неизменяемое связывание
letw=&muty;// неизменяемая ссылка на изменяемое связывание
letr=&muty;// ошибка: нельзя создавать вторую ссылку на изменяемое связывание

*w=90// y = 90
*z=30// ошибка: попытка изменения через ссылку на неизменяемое связывание

letn=Box::new(42);// упаковка
letm=Rc::new(55);// счётчик ссылок
letdata=Arc::new("test_string")// атомарный счётчик
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *