Программирование на rust официальный гайд
Если вы еще не знакомы с Rust, первым делом вам стоит прочитать вводную книгу, Язык программирования Rust. Она покажет вам, что из себя представляет язык, расскажет, как его установить, и объяснит его синтаксис и особенности. По завершении книги вы сможете уверенно программировать на Rust и будете иметь представление об идеях, стоящих за его созданием.
Изучение Rust
Язык программирования Rust. Также известна, как “Книга”. Это всеобъемлющий источник знаний по всем темам, связанным с Rust, и центр всей официальной документации языка.
Rust на примерах. Собрание отдельных, исполняемых в браузере примеров на Rust различной тематики.
Rustonomicon. Книга, полностью посвященная написанию небезопасного кода на Rust. Предназначена для опытных программистов.
rust-learning. Поддерживаемый сообществом список ресурсов для изучения Rust.
Документация
Справка по Rust. Пока у Rust нет спецификации, Справка является попыткой детально описать работу языка. Может содержать неактуальную информацию.
Syntax Index. Это приложение к Книге содержит примеры синтаксиса Rust и ссылки на части Книги, содержащие его объяснение.
Руководство по Cargo. Документация по Cargo, пакетному менеджеру Rust.
Compiler Error Index. Подробное описание ошибок, выводимых компилятором Rust.
Release Notes. Описание изменений в каждой новой версии языка.
Правила проекта
Правила Rust относительно авторского права и торговых знаков. Авторские права на Rust принадлежат Разработчикам Проекта Rust, и его торговый знак принадлежит Mozilla. Здесь описаны случаи, в которых использование символики Rust разрешено.
Правила Поведения. Относятся к организациям Rust на GitHub, официальных форумах, каналах IRC и в других уголках мира Rust.
Документация nightly и beta
Большинство документации Rust также доступно для nightly и beta релизов, в дополнение к стабильной документации выше.
Быстро настройте среду разработки Rust и напишите небольшое приложение!
Установить Rust
Вы можете попробовать Rust онлайн в Rust Playground без установки чего-либо на ваш компьютер.
Rustup: Установщик Rust и инструмент для управления версиями
Основным способом установки Rust, который используют люди, является Rustup - инструмент для установки и управления версиями Rust.
Кажется у вас запущена macOS, Linux или другая Unix-подобная ОС. Для загрузки Rustup и установки Rust, запустите следующее в вашем терминале и следуйте инструкциям на экране.
Похоже, вы работаете под управлением Windows. Чтобы начать использовать Rust, загрузите установщик, затем запустите программу и следуйте инструкциям на экране. Возможно, Вам потребуется установитьVisual Studio C++ Build tools при появлении соответствующего запроса. Если вы не работаете в Windows, смотрите "другие методы установки".
Windows Subsystem for Linux
Если вы используете Windows Subsystem for Linux, для установки Rust запустите следующее в вашем терминале и затем следуйте инструкциям на экране.
Rust запускается на Windows, Linux, macOS, FreeBSD и NetBSD. Если вы используете одну из этих платформ и видите это, то пожалуйста, сообщите о проблеме и следующих значениях:
Если вы используете Unix, то для установки Rust
запустите в терминале следующую команду и следуйте инструкциям на экране.
Если у вас запущен Windows,
скачайте и запустите rustup‑init.exe и затем следуйте инструкциям на экране.
Если у вас запущен Windows,
скачайте и запустите rustup‑init.exe, а затем следуйте инструкциям на экране.
Обновлён ли Rust?
Rust обновляется достаточно часто. Если вы устанавливали Rustup некоторое время назад, есть вероятность что версия Rust устарела. Получите актуальную версию Rust, запустив команду rustup update .
Cargo: Менеджер пакетов и инструмент сборки для Rust
При установке через Rustup, вы получаете последнюю стабильную версию пакетного менеджера и средства сборки Rust, известного, как Cargo. Cargo делает многие вещи:
- собирает ваш проект с cargo build
- запускает ваш проект с cargo run
- тестирует ваш проект с cargo test
- собирает документацию для вашего проекта с cargo doc
- публикует библиотеку на crates.io с cargo publish
Чтобы удостовериться, что Rust и Cargo установлены, вы можете запустить в терминале следующую команду:
Другие инструменты
Поддержка Rust есть во многих редакторах:
Создание нового проекта
Давайте напишем небольшое приложение с нашим новым окружением разработчика. Чтобы начать, мы используем Cargo для создания нового проекта. Запустите в вашем терминале:
cargo new hello-rust
Эта команда создаст новую директорию, зовущуюся hello-rust со следующими файлами:
Cargo.toml - это файл манифеста. Здесь хранятся метаданные вашего проекта, такие как описание.
В файле src/main.rs вы пишете код приложения.
cargo new создал для нас проект "Hello, world!". Для запуска этой программы мы перейдём в директорию, которая была создана, и запустим в терминале:
Вы должны увидеть следующее:
Добавление зависимостей
Давайте добавим зависимость в наше приложение. Вы можете найти разного рода библиотеки на crates.io, реестре пакетов для Rust. В Rust мы обычно называем пакет "crates".
В этом проекте, мы используем пакет с именем ferris-says .
В нашем Cargo.toml файле мы добавим следующую информацию (которую мы получили со страницы пакета):
Теперь мы можем запустить:
. и Cargo установит наши зависимости.
Вы увидите, что эта команда создала новый файл, Cargo.lock . Этот файл представляет собой журнал точных версий зависимостей, которые мы используем локально.
Для использования нашей зависимости, мы можем открыть файл main.rs , удалить всё, что там есть (это просто ещё один пример) и добавить следующую строку:
Это строка означает, что мы теперь можем использовать функцию say , которую нам предоставил пакет ferris-says .
Небольшое приложение на Rust
Теперь давайте напишем небольшое приложение с нашей новой зависимостью. В файл main.rs добавьте следующий код:
После того, как мы это сохраним, мы можем запустить наше приложение набрав:
При условии, что всё пошло хорошо, вы должны увидеть, что ваше приложение вывело на экран следующее:
Узнать больше!
Кто этот краб, Ferris?
Ferris (Феррис) - это неофициальный талисман сообщества Rust. Многие программисты на Rust называют себя "Растациане", обыгрывая слово "crustacean".
Ferris - это имя, обыгрывающее прилагательное "ferrous" ("железистый"), обозначающее "содержащий железо". Поскольку Rust (один из переводов которого - "ржавчина") образуется на железе, название нашего талисмана выглядит забавным!
Привет, Хаброжители! Официальный гайд по языку программирования Rust поможет вам создавать более быстрое и надежное программное обеспечение. Высокоуровневая эргономика и низкоуровневое управление часто противоречат друг другу, но Rust бросает вызов этому конфликту.
Авторы книги входят в команду разработчиков языка, а значит, вы получите всю информацию из первых рук — от установки языка до создания надежных и масштабируемых программ. От создания функций, выбора типов данных и привязки переменных вы перейдете к более сложным концепциям:
- Владение и заимствование, жизненный цикл и типажи.
- Гарантированная безопасность программ.
- Тестирование, обработка ошибок и эффективный рефакторинг.
- Обобщения, умные указатели, многопоточность, типажные объекты и сопоставления.
- Работа со встроенным менеджером пакетов Cargo для создания, тестирования, документирования кода и управления зависимостями.
- Продвинутые средства работы с Unsafe Rust.
Для кого эта книга
Мы предполагаем, что вы писали код на другом языке программирования, но не делаем никаких допущений относительно того, на каком именно. Мы постарались сделать этот материал доступным для тех, кто имеет широкий спектр навыков программирования. Мы не будем тратить время на разговоры о том, что такое программирование. Если вы в программировании абсолютный новичок, то для начала прочтите введение в программирование.
В общем-то, авторы этой книги исходят из того, что вы читаете ее последовательно, от начала до конца. Последующие главы строятся на понятиях предыдущих глав, и в начальных главах мы можем не углубляться в детали по конкретной теме; обычно мы возвращаемся к этой теме в дальнейшем.
В этой книге вы найдете два типа глав: концептуальные и проектные. В концептуальных главах вы будете усваивать тот или иной аспект языка. Мы вместе будем создавать небольшие программы, применяя то, что вы уже усвоили. Главы 2, 12 и 20 посвящены разработке проектов, остальные главы — концептуальные.
В главе 1 рассказано, как установить Rust, написать программу «Hello, World!» и использовать пакетный менеджер и инструмент Cargo. Глава 2 представляет собой практическое введение в язык Rust. Здесь мы рассмотрим понятия с точки зрения высокоуровневого языка, а в последующих главах приведем дополнительные подробности. Если вы хотите сразу же приступить к практике, то сможете это сделать. Можно даже пропустить главу 3, в которой рассматриваются средства языка Rust, аналогичные средствам других языков программирования, сразу перейти к главе 4 и узнать о системе владения в Rust. Но если вы дотошны и предпочитаете разбирать каждую деталь, прежде чем переходить к следующей, то можете пропустить главу 2, перейти к главе 3, а затем вернуться к главе 2, когда захотите поработать над проектом. Так вы сможете применить знания, которые освоили.
В главе 5 обсуждаются структуры и методы, а в главе 6 рассматриваются перечисления, выражения match и конструкция управления потоком if let. Вы будете использовать структуры и перечисления для создания в языке Rust настраиваемых типов.
В главе 7 вы узнаете о системе модулей и правилах конфиденциальности для выстраивания организационной структуры вашего кода и его публичном интерфейсе программирования приложений (API). В главе 8 обсуждаются некоторые часто встречающиеся структуры сбора данных, обеспечиваемые стандартной библиотекой, такие как векторы, строки и хеш-отображения. В главе 9 изучаются философия и методы обработки ошибок.
В главе 10 мы погрузимся в обобщения, типажи и жизненные циклы, которые дают вам возможность определять код, применимый к нескольким типам. Глава 11 полностью посвящена тестированию, которое даже несмотря на гарантии безопасности языка Rust является необходимым для обеспечения правильной логики программы. В главе 12 мы построим собственную реализацию подмножества функциональности инструмента командной строки grep, которая ищет текст внутри файлов. Для этого мы воспользуемся многими понятиями, которые обсуждаются в предыдущих главах.
В главе 13 рассматриваются замыкания и итераторы — средства, которые восходят к функциональным языкам программирования. В главе 14 мы изучим Cargo подробнее и расскажем о лучших практических приемах обмена библиотеками с другими разработчиками. В главе 15 обсуждаются умные указатели, которые обеспечивает стандартная библиотека, и типажи, которые гарантируют их функциональность.
В главе 16 мы рассмотрим разные модели конкурентного программирования и поговорим о том, как Rust помогает вам безбоязненно программировать в множестве потоков исполнения. Глава 17 обращается к сопоставлению идиом Rust с принципами объектно-ориентированного программирования, с которыми вы, возможно, знакомы.
Глава 18 представляет собой справочный материал о паттернах и сопоставлении с паттернами, которые являются мощными способами выражения идей во всех программах на языке Rust. Глава 19 содержит ряд дополнительных тем, представляющих интерес, включая небезопасный код Rust, макрокоманды и другие сведения о типажах, типах, функциях и замыканиях.
В главе 20 мы осуществим проект, в котором выполним реализацию низкоуровневого многопоточного сервера!
Наконец, несколько приложений в конце книги содержат полезную информацию о языке в справочном формате. В приложении А приводятся ключевые слова языка Rust, в приложении Б рассказывается об операторах и символах языка Rust, в приложении В рассматриваются генерируемые типажи, предусмотренные стандартной библиотекой, в приложении Г приводятся некоторые полезные инструменты разработчика, а в приложении Д даются пояснения по поводу редакций языка Rust.
Просто невозможно прочитать эту книгу неправильно: если вы хотите пропустить что-то, пропускайте! В случае если вы почувствуете какую-то путаницу, то, возможно, вам придется вернуться к предыдущим главам. Короче, делайте все, что вам подходит.
Где могут использоваться паттерны
В Rust паттерны много где появляются, и вы часто их использовали, даже не осознавая этого! В данном разделе рассматриваются ситуации, в которых паттерны допустимы.
Ветви выражения match
Как обсуждалось в главе 6, мы используем паттерны в ветвях выражений match. Формально выражения match определяются как ключевое слово match, затем значение для сопоставления и один или несколько ветвей совпадения, состоящих из паттерна и выполняемого выражения, если значение совпадает с паттерном этой ветви, например:
Одно из требований к выражениям match состоит в том, что они должны быть исчерпывающими в том смысле, что в match должны быть учтены все возможные значения. Чтобы вы учитывали все возможные варианты, нужно иметь всеохватывающий паттерн в последней ветви: например, имя переменной, совпадающее с любым значением, всегда будет срабатывать и, таким образом, охватывать все оставшиеся случаи.
Особый паттерн _ будет совпадать с чем угодно, но он не привязывается к переменной и поэтому часто используется в последнем рукаве совпадения. Паттерн _ бывает полезен, например, если вы хотите проигнорировать любое неуказанное значение. Мы рассмотрим паттерн _ подробнее в разделе «Игнорирование значений в паттерне».
Условные выражения if let
В главе 6 мы обсуждали выражения if let главным образом как более краткий способ написания эквивалента выражения match, который совпадает только с одним случаем. Как вариант if let может иметь соответствующий else, содержащий выполняемый код, если паттерн в выражении if let не совпадает.
Листинг 18.1 показывает, что также существует возможность смешивать и сочетать выражения if let, else if и else if let. Благодаря этому мы получаем больше гибкости, чем при использовании выражения match, в котором можно выразить только одно сравниваемое с паттернами значение. Кроме того, условия в серии выражений if let, else if и else if let не обязательно должны относиться друг к другу.
Код в листинге 18.1 показывает серию проверок на несколько условий, которые решают, каким должен быть фоновый цвет. В этом примере мы создали переменные с жестко заданными значениями, которые реальная программа могла бы получить из данных, вводимых пользователем.
Листинг 18.1. Смешивание выражений if let, else if, else if let и else
Если пользователь указывает любимый цвет (1), то это фоновый цвет (2). Если сегодня — вторник (3), то фоновый цвет — зеленый (4). Если пользователь указывает свой возраст в качестве строки и мы можем успешно разобрать ее как число (5), то цвет будет либо фиолетовым (7), либо оранжевым (8) в зависимости от значения числа (6). Если ни одно из этих условий не применимо (9), то фоновый цвет — синий (10).
Эта условная структура позволяет поддерживать сложные требования. С жестко закодированными значениями, которые здесь есть, этот пример будет выводить
Вы видите, что выражение if let может также вводить затененные переменные таким же образом, как и рукава выражения match: строка кода if let Ok(age) = age (5) вводит новую затененную переменную age, содержащую значение внутри варианта Ok. Это означает, что нужно поместить условие if age > 30 в этот блок (6): мы не можем совместить эти два условия в выражении if let Ok (age) = age && age > 30. Затененная переменная age, которую мы хотим сравнить с 30, будет недействительна до тех пор, пока новая область видимости не начнется с фигурной скобки.
Недостатком использования выражений if let является то, что компилятор не проверяет исчерпываемость, в то время как с выражениями match он это делает. Если бы мы пропустили последний блок else (9) и, следовательно, обработку некоторых случаев, то компилятор не предупредил бы нас о возможной логической ошибке.
Условные циклы while let
Похожий по конструкции с выражением if let условный цикл while let позволяет циклу while работать до тех пор, пока паттерн совпадает. Пример в листинге 18.2 показывает цикл while let, который использует вектор в качестве стека и выводит значения в векторе в порядке, обратном тому, в котором они были добавлены.
Листинг 18.2. Использование цикла while let для печати значений, пока метод stack.pop() возвращает Some
В этом примере выводятся 3, 2 и затем 1. Метод pop берет последний элемент из вектора и возвращает Some(value). Если вектор пуст, то pop возвращает None. Цикл while продолжает выполнение кода в своем блоке до тех пор, пока pop возвращает Some. Когда pop возвращает None, цикл останавливается. Мы можем использовать условный цикл while let, чтобы удалить каждый элемент из стека.
Циклы for
В главе 3 мы упоминали, что цикл for — это наиболее распространенная циклическая конструкция в коде на языке Rust, но мы еще не обсуждали паттерн, который берет for. В цикле for паттерном является значение, следующее непосредственно за ключевым словом for, поэтому в for x in y паттерном является x.
Листинг 18.3 показывает использование паттерна в цикле for для деструктурирования, или разложения, кортежа в рамках for.
Листинг 18.3. Использование паттерна в цикле for для деструктурирования кортежа
Код из листинга 18.3 выводит следующее:
Мы используем метод enumerate, чтобы переделать итератор для порождения значения и индекса этого значения в итераторе, помещенных в кортеж. Первый вызов метода enumerate порождает кортеж (0, 'a'). Когда это значение сочетается с паттерном (index, value), то index равен 0, а value равно 'a', выводится первая строка данных.
Инструкции let
До этой главы мы прямо обсуждали использование паттернов только с выражениями match и if let, но на самом деле мы использовали паттерны и в других местах, в том числе в инструкциях let. Рассмотрим простую передачу значения переменной с помощью let:
На протяжении всей книги мы сотни раз использовали инструкции let подобного рода, и хотя вы, возможно, не осознавали этого, вы использовали паттерны! В более формальном плане инструкция let выглядит так:
В таких инструкциях, как let x = 5;, с именем переменной в слоте ПАТТЕРН, имя переменной — это всего лишь простая форма паттерна. Rust сравнивает выражение с паттерном и назначает любые имена, которые он находит. Поэтому в примере let x = 5; паттерном является x, который означает «связать то, что совпадает здесь, с переменной x». Поскольку имя x представляет весь паттерн, то этот паттерн фактически означает «связать все с переменной x, каким бы ни было значение».
Чтобы четче увидеть сопоставление с паттерном инструкции let, рассмотрим листинг 18.4, который использует паттерн с let для деструктурирования кортежа.
Листинг 18.4. Использование паттерна для деструктурирования кортежа и создания сразу трех переменных
Здесь мы сопоставляем кортеж с паттерном. Rust сравнивает (1, 2, 3) с (x, y, z) и видит, что это значение совпадает с паттерном, поэтому Rust связывает 1 с x, 2 с y и 3 с z. Можно думать об этом кортежном паттерне как о вложении в него трех отдельных паттернов переменных.
Если число элементов в паттерне не совпадает с числом элементов в кортеже, то совокупный тип не будет совпадать и мы получим ошибку компилятора. Например, листинг 18.5 показывает попытку деструктурирования кортежа из трех элементов в две переменные, которая не будет работать.
Листинг 18.5. Неправильное построение паттерна, переменные которого не совпадают с числом элементов в кортеже
Попытка компиляции этого кода приводит к ошибке типа:
Если бы мы хотели проигнорировать одно или несколько значений в кортеже, то могли бы использовать _ или . как вы увидите в разделе «Игнорирование значений в паттерне». Если проблема состоит в том, что в паттерне слишком много переменных, то нужно сделать так, чтобы типы совпадали, удалив переменные, чтобы число переменных равнялось числу элементов в кортеже.
Параметры функций
Параметры функций также могут быть паттернами. Код в листинге 18.6, объявляющий функцию foo, которая берет один параметр x типа i32, теперь вам знаком.
Листинг 18.6. Сигнатура функции использует паттерны в параметрах
Часть x — это паттерн! Как и в случае с let, мы можем сопоставить кортеж в аргументах функции с паттерном. Листинг 18.7 разбивает значения в кортеже в момент, когда мы передаем его внутрь функции.
Листинг 18.7. Функция с параметрами, которые деструктурируют кортеж
Этот код выводит
Значения &(3, 5) совпадают с паттерном &(x, y), поэтому x равно 3, а y — 5.
Кроме того, мы можем использовать паттерны в списках параметров замыкания таким же образом, как и в списках параметров функций, поскольку замыкания похожи на функции, как описано в главе 13.
Вы уже увидели несколько способов использования паттернов, но они не работают одинаково всюду, где можно их применить. В некоторых ситуациях эти паттерны должны быть неопровержимы, в других — они могут быть опровержимы. Далее мы обсудим эти два понятия.
Опровержимость: возможность несовпадения паттерна
Паттерны бывают двух видов: опровержимые и неопровержимые. Паттерны, которые будут совпадать с любым возможным переданным значением, неопровержимые. Примером является x в инструкции let x = 5;, потому что x совпадает абсолютно со всем и, следовательно, не может не совпасть. Паттерны, которые не совпадают с некоторыми возможными значениями, являются опровержимыми. Примером служит Some(x) в выражении if let Some(x) = a_value, потому что, если значение в переменной a_value равно None, а не Some, то паттерн Some(x) не совпадет.
Параметры функций, инструкции let и циклы for могут принимать только неопровержимые паттерны, поскольку программа не сможет делать ничего значимого, когда значения не совпадают. Выражения if let и while let принимают только опровержимые паттерны, поскольку по определению они предназначены для обработки возможной ошибки: функциональность условного выражения заключается в его способности выполнять разные действия в зависимости от успеха или провала.
Давайте рассмотрим, что происходит, когда мы пытаемся использовать опровержимый паттерн в месте, где Rust требует неопровержимый паттерн, и наоборот. Листинг 18.8 показывает инструкцию let, но для паттерна мы задали Some(x), опровержимый паттерн. Как и следовало ожидать, этот код не компилируется.
Листинг 18.8. Попытка использовать опровержимый паттерн с let
Если бы значение some_option_value было равно None, то оно не совпало бы с паттерном Some(x), то есть паттерн является опровержимым. Однако инструкция let может принимать только неопровержимый паттерн, поскольку код не может сделать ничего допустимого со значением None. Во время компиляции язык Rust будет жаловаться, что мы пытались использовать опровержимый паттерн там, где требуется неопровержимый паттерн:
Поскольку мы не охватили (и не могли охватить!) каждое допустимое значение с паттерном Some(x), Rust по праву выдает ошибку компилятора.
Листинг 18.9. Использование выражения if let и блока с опровержимыми паттернами вместо let
Код готов! Это абсолютно правильный код, хотя он означает, что мы не можем использовать неопровержимый паттерн без ошибки. Если мы дадим выражению if let паттерн, который всегда будет совпадать, например x, как показано в листинге 18.10, то он компилироваться не будет.
Листинг 18.10. Попытка использовать неопровержимый паттерн с выражением if let
Компилятор жалуется, что использовать выражение if let с неопровержимым паттерном не имеет смысла:
По этой причине рукава выражения match должны использовать опровержимые паттерны, за исключением последнего рукава, который должен сопоставлять любые оставшиеся значения с неопровержимым паттерном. Rust позволяет использовать неопровержимый паттерн в выражении match только с одним рукавом, но этот синтаксис не особо полезен, и его можно заменить более простой инструкцией let.
Теперь, когда вы знаете, где используются паттерны и чем отличаются опровержимые и неопровержимые паттерны, давайте познакомимся с синтаксисом, который мы можем использовать для создания паттернов.
Об авторах
Стив Клабник возглавляет команду по документированию Rust и является одним из ключевых разработчиков языка. Часто выступает с лекциям и пишет много открытого исходного кода. Ранее работал над такими проектами, как Ruby и Ruby on Rails.
Кэрол Николс является членом команды разработчиков Rust Core и соучредителем Integer 32, LLC, первой в мире консалтинговой компании по разработке ПО, ориентированной на Rust. Николс является организатором конференции «Ржавый пояс» (Rust Belt) по языку Rust.
Для Хаброжителей скидка 25% по купону — Rust
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Всем привет. Недавно познакомился с новым для себя языком программирования Rust. Я заметил, что он отличается от других, с которыми мне до этого доводилось сталкиваться. Поэтому решил покопать глубже. Результатами и своими впечатлениями хочу поделиться:
- Начну с главной, на мой взгляд, особенности Rust
- Опишу интересные детали синтаксиса
- Объясню, почему Rust, скорее всего, не захватит мир
Сразу поясню, что я около десяти лет пишу на Java, так что рассуждать буду со своей колокольни.
Rust является низкоуровневым языком, на выходе компилятор выдает бинарник, для работы которого не нужны дополнительные ухищрения. Вся логика по удалению ненужных объектов интегрируется в код в момент компиляции, т.е. сборщика мусора во время выполнения тоже нет. В Rust так же нет пустых ссылок и типы являются безопасными, что делает его даже более надежным чем Java.
В основе управления памятью лежит идея владения ссылкой на объект и одалживания. Если каждым объектом владеет только одна переменная, то как только кончается срок ее жизни в конце блока, все на что она указывала можно рекурсивно очистить. Также ссылки можно одалживать для чтения или записи. Тут работает принцип один писатель и много читателей.
Эту концепцию можно продемонстрировать в следующем куске кода. Из метода main() вызывается test(), в котором создается рекурсивная структура данных MyStruct, реализующая интерфейс деструктора. Drop позволяет задать логику для выполнения, перед тем как объект будет уничтожен. Чем-то похоже на финализатор в Java, только в отличие от Java, момент вызова метода drop() вполне определен.
Вывод будет следующим:
Т.е. перед выходом из test() память была рекурсивно очищена. Позаботился об этом компилятор, вставив нужный код. Что такое Box и Option опишу чуть позже.
Таким образом Rust берет безопасность от высокоуровневых языков и предсказуемость от низкоуровневых языков программирования.
Далее перечислю черты языка по убыванию важности, на мой взгляд.
Тут Rust вообще впереди планеты всей. Если большинство языков пришли к тому, что надо отказаться от множественного наследования, то в Rust наследования нет вообще. Т.е. класс может только имплементировать интерфейсы в любом количестве, но не может наследоваться от других классов. В терминах Java это означало бы делать все классы final. Вообще синтаксическое разнообразие для поддержания OOP не так велико. Возможно, это и к лучшему.
Для объединения данных есть структуры, которые могут содержать имплементацию. Интерфейсы называются trait и тоже могут содержать имплементацию по умолчанию. До абстрактных классов они не дотягивают, т.к. не могут содержать полей, многие жалуются на это ограничение. Синтаксис выглядит следующим образом, думаю комментарии тут не нужны:
Из особенностей на которые я обратил внимание, стоит отметить следующее:
- У классов нет конструкторов. Есть только инициализаторы, которые через фигурные скобки задают значения полям. Если нужен конструктор, то это делается через статические методы.
- Метод экземпляра отличается от статического наличием ссылки &self в качестве первого аргумента.
- Классы, интерфейсы и методы также могут быть обобщенными. Но в отличие от Java, эта информация не теряется в момент компиляции.
Еще немного безопасности
Как я уже говорил Rust уделяет большое внимание надежности кода и пытается предотвратить большинство ошибок на этапе компиляции. Для этого была исключена возможность делать ссылки пустыми. Это мне чем-то напомнило nullable типы из Kotlin. Для создания пустых ссылок используется Option. Так же как и в Kotlin, при попытке обратиться к такой переменной, компилятор будет бить по рукам, заставляя вставлять проверки. Попытка же вытащить значение без проверки может привести к ошибке. Но этого уж точно нельзя сделать случайно как, например, в Java.
Мне еще понравилось то, что все переменные и поля классов по умолчанию являются неизменяемыми. Опять привет Kotlin. Если значение может меняться, это явно надо указывать ключевым словом mut. Я думаю, стремление к неизменяемости сильно улучшает читабельность и предсказуемость кода. Хотя Option почему-то является изменяемым, этого я не понял, вот код из документации:
Перечисления
В Rust называются enum. Только помимо ограниченного числа значений они еще могут содержать произвольные данные и методы. Таким образом это что-то среднее между перечислениями и классами в Java. Стандартный enum Option в моем первом примере как раз принадлежит к такому типу:
Для обработки таких значений есть специальная конструкция:
А также
Я не ставлю себе целью написать учебник по Rust, а просто хочу подчеркнуть его особенности. В этом разделе опишу, что еще есть полезного, но, на мой взгляд, не такого уникального:
- Любители функционального программирования не будут разочарованы, для них есть лямбды. У итератора есть методы для обработки коллекции, например, filter и for_each. Чем-то похоже на стримы из Java.
- Конструкция match так же может быть использована для более сложных вещей, чем обычные enum, например, для обработки паттернов
- Есть большое количество встроенных классов, например, коллекций: Vec, LinkedList, HashMap и т.д.
- Можно создавать макросы
- Есть возможность добавлять методы в существующие классы
- Поддерживается автоматическое выведение типов
- Вместе с языком идет стандартный фреймворк для тестирования
- Для сборки и управления зависимостями используется встроенная утилита cargo
Этот раздел необходим для полноты картины.
Killer problem
Главный недостаток происходит из главной особенности. За все приходится платить. В Rust очень неудобно работать c изменяемыми графовыми структурами данных, т.к. на любой объект должно быть не более одной ссылки. Для обхода этого ограничения есть букет встроенных классов:
- Box — неизменяемое значение на куче, аналог оберток для примитивов в Java
- Cell — изменяемое значение
- RefCell — изменяемое значение, доступное по ссылке
- Rc — reference counter, для нескольких ссылок на один объект
И это неполный список. Для первой пробы Rust, я опрометчиво решил написать односвязный список с базовыми методами. В конечном счете ссылка на узел получилась следующая Option<Rc<RefCell<ListNode>>>:
- Option — для обработки пустой ссылки
- Rc — для нескольких ссылок, т.к. на последний объект ссылаются предыдущий узел и сам лист
- RefCell — для изменяемой ссылки
- ListNode — сам следующий элемент
Выглядит так себе, итого три обертки вокруг одно объекта. Код для простого добавления элемента в конец списка получился очень громоздкий, и в нем есть неочевидные вещи, такие как клонирования и одалживания:
На Kotlin то же самое выглядит намного проще:
Как выяснил позже подобные структуры не являются характерными для Rust, а мой код совсем неидиоматичен. Люди даже пишут целые статьи:
Тут Rust жертвует читабельностью ради безопасности. Кроме того такие упражнения еще могут привести к зацикленным ссылкам, которые зависнут в памяти, т.к. никакой garbage collector их не уберет. Рабочий код на Rust я не писал, поэтому мне сложно сказать насколько такие трудности усложняют жизнь. Было бы интересно получить комментарии практикующих инженеров.
Сложность изучения
Долгий процесс изучения Rust во многом следует из предыдущего раздела. Перед тем как написать вообще хоть что-то придется потратить время на освоение ключевой концепции владения памятью, т.к. она пронизывает каждую строчку. К примеру, простейший список у меня занял пару вечеров, в то время как на Kotlin то же самое пишется за 10 минут, при том что это не мой рабочий язык. Помимо этого многие привычные подходы к написанию алгоритмов или структур данных в Rust будут выглядеть по другому или вообще не сработают. Т.е. при переходе на него понадобится более глубокая перестройка мышления, просто освоить синтаксис будет недостаточно. Это далеко не JavaScript, который все проглотит и все стерпит. Думаю, Rust никогда не станет тем языком, на котором учат детей в школе программирования. Даже у С/С++ в этом смысле больше шансов.
Мне показалась очень интересной идея управления памятью на этапе компиляции. В С/С++ у меня опыта нет, поэтому не буду сравнивать со smart pointer. Синтаксис в целом приятный и нет ничего лишнего. Я покритиковал Rust за сложность реализации графовых структур данных, но, подозреваю, что это особенность всех языков программирования без GC. Может быть, сравнения с Kotlin было и не совсем честным.
В этой статье я совсем не коснулся многопоточности, думаю это отдельная большая тема. Еще есть планы написать какую-нибудь структуру данных или алгоритм посложнее списка, если есть идеи, прошу поделиться в комментариях. Интересно было бы узнать приложения каких типов вообще пишут на Rust.
Если вас заинтересовал Rust, то вот несколько ссылок:
-
— хорошая книга, есть так же в электронном варианте — официальная документация, есть примеры — список статей и ruRust/general — каналы в Gitter — Reddit
UPD: Всем спасибо за комментарии. Узнал много полезного для себя. Исправил неточности и опечатки, добавил ссылок. Думаю, такие обсуждения сильно способствуют изучению новых технологий.
Читайте также: