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

  • Как убедиться, что в моем приложении нет проблем и уязвимостей, связанных с неправильной работой с памятью?

  • Как быть уверенным в том, что любой доступ к общим объектам правильно защищен?

  • Как свести к минимуму любую работу, не связанную напрямую с написанием кода?

Цель данной статьи не рассказать о доселе невиданных возможностях Rust (сразу говорю, что тут ничего нового "Растоманы" не найдут), на Хабре вы итак найдете множество интересных статей о внутренностях языка и интересных случаев использования. Моя цель рассказать о том, что он предлагает в качестве решения обозначенных выше проблем, как это будет выглядеть со стороны программиста, и почему это важно.

История одного языка

Все началось в 2006 году как персональный проект сотрудника Mozilla - Грейдона Хоара (Graydon Hoare). В 2009 году Грейдон представил свой новый язык программирования коллегам из Mozilla Research, предложив его как перспективную замену C++, поскольку он разрабатывался с приоритетом на безопасность и эффективность создания параллельных приложений (современные браузеры, такие как Mozilla Firefox, являются крупными и очень сложными проектами с не тривиальными алгоритмами параллельной обработки данных). Вскоре, на ежегодном саммите Mozilla 2010, Грейдон представил язык Rust и анонсировал проект Servo (браузерный движок нового поколения). Вначале сообщество разработчиков отнеслось к этой инициативе довольно скептически и некоторые обвиняли Mozilla в синдроме NIH (Not Invented Here). Они назвали его ещё одним бесполезным языком программирования, который умрёт ещё до того, как он станет достаточно популярным, чтобы иметь хоть какое-то значение (вспоминая язык D и его судьбу). Однако дальнейшее развитие событий показало, что это не очередной академический проект для тестирования идей, а полноценный конкурент для таких мастодонтов, как C/C++.

В 2011 г. Rust, используя LLVM, успешно скомпилировал сам себя, а в 2015 году была выпущена первая стабильная версия 1.0. И, наконец, в 2017 году Mozilla выпустила свой первый полноценный проект, написанный на Rust, который был успешно интегрирован в Firefox - Quantum CSS - полностью мультипоточный CSS-движок, в основу которого взят результат разработки прототипа Servo. На этом этапе Mozilla доказала, что язык Rust уже зрелый и обладает достаточной функциональностью для создания больших и сложных production-ready приложений.

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

В последние годы мнение разработчиков о языке изменилось в положительную сторону. Невероятно, но Rust является самым любимым языком пользователей Stack Overflow уже пять лет подряд! Более того, стали появляться проекты, целиком написанные на Rust, а его присутствие в популярных open-source и коммерческих проектах растет с каждым годом. Например, ripgrep - это проект с открытым исходным кодом для рекурсивного поиска в каталогах с использованием регулярных выражений. Он в 10 раз быстрее, чем GNU grep, и в 4 раза быстрее, чем Silver Searcher. В результате ripgrep был интегрирован в MS Visual Studio Code в качестве средства поиска регулярок по умолчанию. Также, в копилку доказательства хорошей производительности получаемых программ можно добавить результаты The Computer Language Benchmarks Game проекта Debian, где Rust в одной лиге с C/C++.

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

  • Apple – технологический гигант, который ставит на первое место производительность и безопасность, решил перевести низкоуровневую часть Apple Cloud Traffic на Rust, а новый функционал, по возможности, в первую очередь писать на нем.

  • Amazon – выбрал Rust для реализации Firecracker – проекта безопасных и быстрых microVM для бессерверных вычислений.

  • Microsoft – также активно исследует возможность перехода на Rust для низкоуровневых и чувствительных к производительности компонентов. Они уже попробовали в качестве эксперимента переписать некоторые низкоуровневые системные компоненты Windows.

  • Google – разрешает использовать Rust (кроме kernel части) для реализации своей новой операционной системы Fuchsia.

  • Mozilla – собирается переписать внутренние компоненты на Rust, чтобы улучшить многопоточную обработку, как часть инициативы сделать Firefox более безопасным и быстрым - Firefox Quantum.

  • Huawei – также исследует возможности использовать язык Rust в своих будущих проектах.

Список можно продолжить и далее: Dropbox, Facebook, Bitbucket, Discord и т.д. – все эти известные компании начали использовать Rust в частях своих серверных платформ. Обратите внимание, что не только крупные международные компании используют Rust – в нем заинтересованы и стартапы. Например, новая компания, основанная Брайаном Кантриллом (изначальный разработчик dtrace), планирует использовать Rust в качестве основного языка для реализации своего проекта. Немаловажно для всех этих компаний то, что разработчики Rust принимают решения, связанные с дизайном языка, руководствуясь обратной совместимостью и стабильностью кодовой базы.

Но, несмотря на все успехи Rust, колыбель языка Mozilla чувствовала себя не очень хорошо и, в связи с финансовыми трудностями, в 2020 году приступила к отделению проекта в независимую организацию Rust Foundation, серьезно уменьшив свое влияние на развитие языка и отпустив его в свободное плавание. Изначальный список членов организации внушает оптимизм за будущее языка:

Как вы можете видеть, в данный момент Rust является одним из самых любимых и перспективных языков программирования. Было бы ошибкой для программистов просто игнорировать все эти факты и ни разу не попробовать данный язык в деле.

Но что делает Rust таким желанным для многих разработчиков? Давайте немного углубимся в детали и изучим некоторые важные и полезные аспекты языка.

Приоритет безопасности

Если мы хотим создать приложения с минимумом уязвимостей, мы должны обратить пристальное внимание на операции управления памятью. Нарушение безопасности памяти приводит к неожиданному сбою программ и может быть использовано для несанкционированного доступа к данным пользователя. В результате пользователь сталкивается с утечкой данных и удаленным выполнением кода. К таким нарушениям относятся:

  • Использование памяти после освобождения (Use After Free)

  • Разыменование нулевого указателя (Null Dereference)

  • Использование неинициализированной памяти (Using uninitialized variables)

  • Двойное освобождение (Double Free)

  • Переполнение буфера (Buffer Overflow)

MSRC Security Research 2019 года (Microsoft) показал, что 70% Common Vulnerabilities and Exposures (CVEs) в их продуктах были связаны с проблемами безопасности памяти в С и С++ (думаю весьма известный график):

Кто-то может сказать, что данный отчет лишь показывает проблемы Microsoft, так как их продукты являются проприетарным программным обеспечением, тогда как продукт с открытым исходным кодом благодаря контролю «тысячи глаз» будет иметь значительно меньше проблем. На самом деле не все так радужно. Взгляните на CVEs за 2019 год (последний из доступных на данном ресурсе), найденные в ядре Linux, разрабатываемом и проверяемом ведущими отраслевыми экспертами:

Как мы видим, open-source сообщество допустили достаточно большое количество уязвимостей, связанных с безопасностью работы с памятью (примерно 65%). Я уверен, вы знаете, что такого рода проблемы тяжело найти и зачастую они проходят мимо глаз. Однако в Rust эти проблемы не могут возникнуть, как говорится, by design (если конечно не использовать unsafe, но это тема для отдельного разговора). И все это достигается на этапе компиляции!

Для этих целей Rust имеет такие элементы языка, как Ownership и Borrowing. Система Ownership является основой для всего языка. Rust гарантирует, что у любого заданного объекта на все время его жизни существует ровно один владелец:

fn main() {
    let a = String::from("Hello"); // Аллоцируем объект на куче
    let b = a; // меняем владение объектом с a на b
    // let c = a; // Ошибка компиляции
    println!("{}", b); // b теперь владелец строки
}

Кроме того, передача объекта в качестве аргумента функции также приведет к передаче права собственности на область действия аргумента функции:

fn do_smth(a: String) {
    // а владеет объектом
    println!("{}", a);
}
  
fn main() {
    let a = String::from("Hello");
    // Переносим объект в область видимости функции
    do_smth(a);
    // let b = a; // Ошибка компиляции
}

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

  • Объясняет, почему проблема произошла;

  • Выделяет все элементы, которые вызвали проблему;

  • Дает инструкции о том, как получить больше информации об ошибке, и даже предлагает возможный вариант исправления.

Для примера, посмотрите на это подробное сообщение об ошибке:

error[E0382]: use of moved value: `a`
--> src/main.rs:4:13
|
2 | let a = String::from("Hello");
| - move occurs because `a` has type `std::string::String`, which does not implement the `Copy` trait
3 | let b = a;
| - value moved here
4 | let c = a;
| ^ value used here after move
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.

Хорошо, а когда освободится выделенная память? В Rust нету GC, поэтому волшебный runtime не придет на помощь. В таких языках как C/C++ разработчик обязан самостоятельно определять, когда пора возвратить память ОС. Данная задача не из тривиальных и требует достаточно высокую квалификацию в сложных проектах. Rust решает эту проблему по-своему: он позволяет хранить данные как в стеке, так и в куче, а память автоматически возвращается, как только переменная, которой она принадлежит, уходит за пределы области видимости:

{
    let s = String::from("hello"); // создаем строку s
    // различные операции над s
} // С этого момент s вышла за предел области видимости и может быть удалена

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

  • В любой момент времени вы можете иметь либо одну изменяемую ссылку, либо любое количество неизменяемых ссылок.

  • Ссылки всегда должны быть актуальными.

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

fn do_smth(a: &String) {
    println!("{}", a); // Немутабельная операция
}
  
fn main() {
    let a = String::from("Hello");
    do_smth(&a); // одалживаем объект у a
    println!("{}", a); // a все еще владелец объекта
}

А вот так мы можем нарушить озвученные выше ограничения. Но тут приходит borrow checker, и выписывает нам ошибку:

fn main() {
    let mut s = String::from("hello");
  
    let r1 = &mut s;   
    let r2 = &mut s; // Здесь ошибка компиляции
  
    println!("{}, {}", r1, r2);
}

В данном примере мы нарушаем одно из правил языка – в один момент времени разрешена только одна изменяемая ссылка. В этом случае мы получаем следующее сообщение:

error[E0499]: cannot borrow `s` as mutable more than once at a time
--> wiki/src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here

Эти ограничения позволяют производить мутации, но в строго контролируемом стиле. Это то, с чем новые разработчики имеют наибольшее количество проблем, потому что такие языки как C/C++ позволяют мутировать объекты без каких-либо ограничений. Все эти правила были введены, чтобы предотвратить одну очень неприятную проблему при разработке многопоточных программ - data race. Но об этом поговорим чуть позже.

Rust - это язык со статической типизацией, который дополнительно имеет следующие особенности:

  • Разделяет переменные на изменяемые и неизменяемые.

  • Каждая переменная является неизменяемой по умолчанию и обязательно должна быть проинициализирована актуальным для данного типа значением (например, определенным значением enum).

  • Не позволяет выполнять неявное приведение типов даже для примитивов.

  • Проверка границ массивов (небольшая цена производительности для исключения более серьезных проблем).

  • В debug-режиме компилируется с проверкой integer overflow.

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

И под конец главы добавлю, что сами разработчики Rust не только о себе думают, но и активно участвуют в улучшении безопасности проекта LLVM. Например в этом году они в тесной коллаборации с LLVM добавили поддержку защиты от Stack Clash в LLVM/Clang.

Многопоточность без гонок

Последние поколения процессоров склонны наращивать количество ядер. Еще несколько лет назад максимальное количество ядер в корпоративных процессорах составляло 28, но не так давно получили распространение процессоры AMD Epyc и Kunpeng, которые подняли планку до 64 ядер! Это огромная мощность, которую очень трудно использовать эффективно. Максимальное использование ресурсов таких CPUs приводит к тому, что алгоритмы и приложения становятся все более сложными. Исторически, программирование в этом контексте всегда было трудоемким и подвержено ошибкам, поэтому разработчикам нужны инструменты, которые помогут им реализовать свои идеи без головной боли.

Fearless Concurrency - этим термином разработчики Rust называют способность своего языка помогать создавать многопоточные алгоритмы безопасно и эффективно. Давайте начнем с простого примера, как создать поток:

use std::thread;
  
fn main() {
    // Создаем поток и запускаем на нем анонимную функцию
    let handler = thread::spawn(|| {
        // Тело функции
        println!("I'm thread");
    });
    // Родительский процесс выполняется здесь
    println!("I'm main thread");
    // Ожидаем пока завершится поток
    handler.join().unwrap();
}

В Rust потоки изолированы друг от друга с помощью системы владения объектом. Записи могут происходить только в том случае, если поток является владельцем данных или имеет изменяемую ссылку. Таким образом, в любой момент времени существует только один поток, который имеет мутабельный доступ к данным. Чтобы ограничить передачу данных между потоками, Rust поддерживает два типажа (traits): Sync и Send. Оба этих типажа реализуются автоматически, когда компилятор определяет, что тип данных соответствуют требованиям. И тут мы уже увидели одну очень приятную особенность Rust: язык различает безопасные и небезопасные типы для передачи данных между потоками .

Send – это типы, которые могут быть безопасно перемещены из одного потока в другой. Например, Arc (Atomic Reference Counter) и String - это типы Send. Один из способов передачи данных между потоками - каналы в стиле Go. Это потокобезопасная операция, и компилятор проверяет, реализует ли объект Send. Вот пример переноса объекта:

fn main() {
    // Создаем канал на передачи данных
    let (sender, receiver) = channel();
    // Создаем новый поток и передаем туда sender
    thread::spawn(move || {
        // Создаем объект
        let hello = String::from("Hello");
        // Отправляем объект через канал
        sender.send(hello).unwrap();
        // Начиная с этого места, владение объектом передано через канал
        // let tmp = hello; // Ошбика компиляции
    });
    // Получаем данные из канала
    let data = receiver.recv().unwrap();
    // Печатаем строку "Hello"
    println!("{}", data);
}

Если разработчик попытается переместить объект, не являющийся объектом Send, в другой поток, используя захват переменной замыканием, компилятор Rust выведет нечто подобное:

`std::rc::Rc<i32>` cannot be sent between threads safely

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

fn main() {
    // Создаем объект
    let tmp = 42;
    // Отправляем владение объектом в Arc
    let data = Arc::new(tmp);
    // Создаем новую ссылку на Arc
    let data_ref = data.clone();
    // Создаем поток и запускаем функцию в нем
    let thread = thread::spawn(move || {
        // Разыменовываем объект из Arc и печатаем его
        println!("{}", *data_ref);
    });
    // Печатаем объект
    println!("{}", *data);
    // Синхронизируем потоки
    thread.join().unwrap();
}

В случаях, когда требуется потокобезопасная мутабельность, Rust предоставляет атомарные типы данных и исключающую блокировку через Mutex. Основное преимущество подхода Rust - это то, что нет простого способа получить доступ к объекту без блокировки мьютекса или без использования атомарного типа данных. Например, когда вы создаете мьютекс, вы также передаете в него права на собственность объекта. Метод Lock создает некий smart-pointer MutexGuard на объект внутри мьютекса, через который вы можете получить доступ к объекту в этом потоке, пока у MutexGuard не закончится время жизни:

// Аллоцируем на куче
let s = String::from("Hello, ");
// Переносим владение объектом в Mutex
let mutex = Mutex::new(s);
// Ошибка: s больше не владелец
// s.push_str("World!");
// Блокируем мьютекс и создаем MutexGuard
let mut d = mutex.lock().unwrap();
d.push_str("World!");
// Вывод: Hello, World
println!("{}", d);
// MutexGuard освобождается при выходе из области видимости

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

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

One Tool to Rule Them All

Что мы узнали: Rust - это уникальный язык, который может сделать жизнь разработчиков программного обеспечения проще (или сложнее), а их программы безопаснее (хотя и требует более долгого времени вхождения до начала эффективного применения). Но есть еще один чрезвычайно важный аспект всех языков программирования, который требует отдельного внимания - инструментарий. Давайте вспомним, какой инструментарий используется в C/C++? На самом деле, на это трудно ответить, потому что каждый проект выбирает свой пакетный менеджер, билд-систему, формат документации и систему тестирования. Это вызывает трудности в обслуживании продуктов у мейнтейнеров.

Разработчики Rust знают об этой ситуации и понимают, что они должны обязательно предложить нечто лучшее, чтобы обеспечить все необходимые функции и уменьшить трату времени на задачи, которые не связаны непосредственно с разработкой продукта. Результат их работы они назвали Cargo. Cargo поставляется вместе с Rust и используется почти в каждом проекте (хотя без него вы все еще можете использовать компилятор rustc, как любой другой компилятор C/C++). Этот менеджер берет все процедуры обслуживания на себя и помогает вам создавать проекты, управлять зависимостями, собирать приложение, создавать документацию и тестировать проект.

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

cargo new test-proc

Cargo создаст каталог со следующими файлами:

.
+-- .git
+-- .gitignore
+-- Cargo.lock
+-- Cargo.toml
+-- src
L-- main.rs

Как мы видим, Cargo создал git репозиторий, каталог с исходниками, TOML-файл, содержащий конфигурацию проекта, и некоторые служебные файлы. Самый интересный файл TOML. Здесь хранятся все настройки проекта, включая имя проекта, версию, список зависимостей, флаги для компилятора и другие классные вещи. Его легко читать и редактировать, а так же централизовано управлять релизами. Пример содержания файла после инициализации:

[package]
name = "test-proc" # Имя проекта
version = "0.1.0"  # Версия в нотации Semver
authors = ["hackerman"] # Список авторов
edition = "2018"   # Версия Rust

Далее, давайте напишем простенькую программу:

fn add(a: u32, b: u32) -> u32 {
    a + b
}
 
fn main() {
    println!("Data = {}", add(1, 2));
}

И соберём ее:

cargo build
Compiling test-proc v0.1.0 (/home/hackerman/projects/test-proc)
Finished dev [unoptimized + debuginfo] target(s) in 0.38s

Это все, что требуется от разработчика, чтобы собрать проект со всеми зависимостями. И, самое главное, при росте проекта сложность управления не сильно изменится благодаря продуманной структуре проекта и инструментов. Cargo проверяет все изменения, сканирует зависимости и обновляет их при необходимости, используя для конфигурации TOML файл.

Тестирование

Rust и Cargo уже имеют все, что нужно для написания тестов без каких-либо внешних инструментов. Вкратце, тест в Rust — это функция, аннотированная атрибутом #[test]. Давайте для примера создадим тестовую функцию, добавив несколько строк кода в main.rs:

// Атрибут для тестовой функции
#[test]
// Имя функции это имя теста
fn it_works() {
    // Тело теста
    assert_eq!(add(2, 2), 4);
}

Готово! Теперь запускаем:

cargo test
Compiling test-proc v0.1.0 (/home/hackerman/projects/test-proc)
Finished test [unoptimized + debuginfo] target(s) in 0.53s
Running target/debug/deps/test_proc-7c55eae116dd19b3
  
running 1 test
test it_works ... ok
  
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

С помощью атрибута #[cfg(test)] можно создать отдельный подмодуль со всеми нужными вспомогательными функциями для тестов который будут использоваться только во время тестирования.

Документация

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

/// Sums two values.
///
/// # Examples
///
/// ```
/// let arg1 = 6;
/// let arg2 = 1;
/// let answer = testproc::add(arg1, arg2);
///
/// assert_eq!(add(6, 1), answer);
/// ```
fn add(a: u32, b: u32) -> u32 {
    a + b
}

И собираем документацию одной простой командой:

cargo doc
 Documenting test-proc v0.1.0 (/home/hackerman/projects/test-proc)
    Finished dev [unoptimized + debuginfo] target(s) in 0.82s

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

Внешние зависимости

В Rust единица компиляции называется "crate" и может быть либо библиотекой, либо исполняемой программой.

Например, нам нужно проанализировать некоторые JSON-файлы, но мы не хотим делать это с нуля. В этом случае мы идем на постоянно растущий crates.io репозиторий и ищем подходящий crate для нас. Отлично! Мы нашли Serde - библиотека для сериализации и десериализации структур данных. Давайте попробуем импортировать ее с поддержкой json в наш проект с помощью TOML-файла:

[dependencies]
serde_json = "1.0.53"

И вызываем команду сборки проекта:

> cargo build
   Compiling serde v1.0.110
   Compiling ryu v1.0.4
   Compiling itoa v0.4.5
   Compiling serde_json v1.0.53
   Compiling test-proc v0.1.0 (/home/hackerman/projects/test-proc)
    Finished dev [unoptimized + debuginfo] target(s) in 7.41s

Cargo проверил все зависимости, загрузил их и собрал. Теперь вы можете пользоваться библиотекой напрямую! Раздел [dependencies] - это место, где вы сообщаете Cargo требуемые крейты и их версии. В этом примере мы задаем семантический спецификатор версии 1.0.53. Cargo понимает Semver нотацию, и это фактически означает "любую версию, совместимый с версией 1.0.53". Для обновления крейтов до новой версии достаточно воспользоваться командой cargo update. Кроме того, любой может поделиться своими собственным крейтом на crates.io, и вы можете это сделать лишь парой команд! С помощью крейтов можно установить дополнительные инструменты в рабочее пространство (например, ripgrep) для различных целей вашего проекта. Вдобавок, вы можете установить свой собственный сервер crate.io в сети вашей организации и быть независимыми от внешних поставщиков. Короче говоря, замечательный инструмент, который делает разработку на Rust такой гладкой и беспроблемной.

Среды разработки

Последнее, но не менее важное, о чем я хочу вам рассказать - это rust-analyzer (будущий преемник RLS), предоставляющий поддержку Rust в различных средах разработки. В данный момент, с использованием rust-analyzer или своей собственной реализации анализатора, уже поддерживаются следующие IDE и редакторы: vim, emacs, MS Visual Studio Code, IntelliJ Idea и т.д. Они имеют все необходимые функции для эффективного написания кода, такие как: дополнение кода, вывод документации и подсветка ошибок. Количество поддерживаемых интегрированных сред достаточно велико, чтобы вы могли себя чувствовать, как дома.

Эпилог

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