Rust (язык программирования)
Rust (Раст, [rʌst]; rust с англ. — «ржавчина») — мультипарадигменный компилируемый язык программирования общего назначения, сочетающий парадигмы функционального и процедурного программирования с объектной системой, основанной на типажах. Управление памятью осуществляется через механизм «владения» с использованием аффинных типов[англ.][10], что позволяет обходиться без системы сборки мусора во время исполнения программы. Rust гарантирует безопасную работу с памятью благодаря встроенной в компилятор системе статической проверки ссылок (borrow checker). Имеются средства, позволяющие использовать приёмы объектно-ориентированного программирования[11]. Ключевые приоритеты языка: безопасность, скорость и параллелизм. Rust пригоден для системного программирования, в частности, он рассматривается как перспективный язык для разработки ядер операционных систем[10]. Rust сопоставим по скорости и возможностям с C++/Си, однако даёт большую безопасность при работе с памятью, что обеспечивается встроенными в язык механизмами контроля ссылок. Производительности программ на Rust способствует использование «абстракций с нулевой стоимостью»[12]. После нескольких лет активной разработки первая стабильная версия (1.0) вышла 15 мая 2015 года, после чего новые версии выходят раз в 6 недель[13]. Для версий языка, вышедших после 1.0, заявлена обратная совместимость[14]. Разрабатывается с 2010-х годов сообществом Mozilla Research и финансировался фондом Mozilla Foundation. С 2020 года планировалась передача интеллектуальной собственности и процессов развития и финансирования языка в организацию Rust Foundation[15]. 8 февраля 2021 года пять компаний-учредителей (AWS, Huawei, Google, Microsoft и Mozilla) официально объявили о создании Rust Foundation[16][17]. Девять лет подряд с 2016 по 2024 год Rust занимает первое место в списке самых любимых языков программирования («Most loved programming languages» / «Most admired programming languages») по версии ежегодного опроса разработчиков Stack Overflow Developer Survey[18][19][20][21][22][23]. ИсторияРабота над языком была начата сотрудником Mozilla Грэйдоном Хором в 2006 году. Автор дал проекту название Rust, по его словам, связанное с грибами семейства ржавчинные (англ. rust fungi)[24]. В 2009 году[25] компания Mozilla начала отдельно спонсировать разработку Rust. Спустя год язык был официально представлен на Mozilla Summit 2010[26]. Изначальный компилятор, реализованный на OCaml, был заменён на новый, написанный на Rust и использовавший LLVM для генерации машинного кода[27]; в следующем году новый компилятор впервые успешно скомпилировал сам себя[28]. Первая официальная альфа-версия Rust (0.1) была выпущена в январе 2012 года[29]. В апреле 2013 года был запущен Servo — экспериментальный проект компании Mozilla по разработке браузерного движка на Rust.[30] Первая стабильная версия Rust (1.0) вышла в мае 2015 года. Программные интерфейсы и возможности языка подверглись значительной ревизии, после которой по умолчанию оставлены только полностью готовые к применению возможности, реализация которых не будет изменяться в дальнейшем. Все остальные функции переведены в разряд экспериментальных и вынесены из поставки по умолчанию[31]. В декабре 2022 года Rust стал первым языком, кроме C и ассемблера, который поддерживается при разработке ядра Linux[32]. Microsoft переписывает отдельные элементы собственной экосистемы программных продуктов на языке программирования Rust; так, в 2024 г. фундаментальный серверный компонент, обеспечивающий работу набора облачных сервисов Microsoft 365 будет переписан на этом языке[33]. Система типовИспользуется сильная статическая типизация. Поддерживается обобщённое программирование с поддержкой параметрического полиморфизма, обеспечивается автоматический вывод типов для локальных переменных (но не для параметров функций). Реализована поддержка единичных типов[англ.] данных — типов, которые имеют ровно один экземпляр и не занимают места в памяти, примеры:
Реализованы пустые типы[англ.] данных — типы, экземпляры которых не могут быть созданы; реализованы в виде перечисляемых типов, не имеющих вариантов: Все типы данных в языке делятся на две основные группы: простые и типы стандартной библиотеки. Простые типы (типы постоянной длины, встроенные в сам язык) — числовой, булев, символьный, массив, срез, строковый срез, кортеж, ссылка, указатель на функцию. Часть простых типов является «машинной», то есть реализуются непосредственно в современных процессорах, таковы числовой, булев и символьный. Типы, предоставляемые стандартной библиотекой Числовые типы:
Булев (bool): Символьный (char): тип, представляющий символ Unicode (внутреннее представление данных как u32). Примеры значений: Указатель на функцию (function pointer): объекты-функции имеют тип, определяемый их сигнатурой, то есть параметрами и возвращаемым значением. Пример: Ссылка (разделяемое заимствование — shared borrow) Ссылка изменяемая (изменяемое заимствование — mutable borrow) Структуры (struct):
Перечисление (enum): каждый вариант в перечислении в Rust может быть также связан с другими данными, благодаря чему перечисление называют также tagged union или типом-суммой. Синтаксис для объявления вариантов схож с синтаксисом для объявления структур: могут быть варианты без данных, варианты с именованными данными и варианты с безымянными данными:
При выборе следует отдавать предпочтение Управление памятьюВ языке реализована модель управления памятью, ориентированная на безопасные шаблоны параллельного выполнения, препятствующая некорректному доступу к памяти, который обычно является источником критических ошибок сегментации в других языках программирования. Обеспечивается контроль над использованием неинициализированных и деинициализированных переменных; невозможно совместное использование разделяемых состояний несколькими задачами; обеспечивается статический анализ времени жизни указателей и проверка на выход за пределы массива (автоматически и всегда, но доступно отключение проверки в Реализована так называемая Move-семантика: по умолчанию Rust «переносит» (move) указатель на объект в куче новому владельцу при присваивании, делая старую переменную недействительной. Этого не происходит, если тип реализует типаж Copy, поскольку данные в стеке копируются. let a = "объект с данными в куче".to_string();
// объект передан переменной b
// переменная a становится неинициализированной
let b = a;
// ошибка!
let c = a;
// данные объекта на стеке
let a = 55;
// копия объекта передана переменной b
let b = a;
// c = 55
let c = a;
Ещё одна особенность модели памяти — поддержка заимствований (borrow) с возможностью изменения заимствованного объекта ( Упаковка (Box) — «умный» указатель, владеющий объектом в куче, уничтожает объект и освобождает память при выходе из области видимости. Ячейка (Cell, RefCell) реализует изменяемость содержимого при неизменяемости самой ячейки. Указатели со счётчиком ссылок ( «Сырые» указатели неизменяемые ( Связывания неизменяемы по умолчанию, а чтобы объявить переменную изменяемой, необходимо ключевое слово mut. Примеры: let x = 80; // связывание владельца x со значением 80
let mut y = 50; // изменяемое связывание
let z = &x; // неизменяемая ссылка на неизменяемое связывание
let w = &mut y; // неизменяемая ссылка на изменяемое связывание
let r = &mut y; // ошибка: нельзя создавать вторую ссылку на изменяемое связывание
*w = 90 // y = 90
*z = 30 // ошибка: попытка изменения через ссылку на неизменяемое связывание
let n = Box::new(42); // упаковка
let m = Rc::new(55); // счётчик ссылок
let data = Arc::new("test_string") // атомарный счётчик
В своей докторской диссертации Ральф Юнг (англ. Ralph Jung) формально доказал потокобезопасность и безопасность управления памятью, использовав логику разделения в созданной им модели RustBelt и инструменте Iris (основанном на Coq)[34]. СинтаксисСинтаксис языка похож на Си и C++; язык регистрозависимый, блоки кода ограничиваются фигурными скобками; используются стандартные наименования управляющих конструкций if, else, while, и for; комментарии также пишутся в С-формате; имена модулей разделяются двумя символами двоеточия ( Набор операторов в Rust: арифметические ( Rust поддерживает макроопределения — средства подстановки с использованием регулярных выражений, выполняющиеся во время этапа подготовки к компиляции, более развитые и безопасные, чем в Си. Макроопределения (макрокоманды) — это определяемые пользователем простые расширения синтаксиса, выполняемые с помощью команды Связывание имёнКлючевое слово let x: i32 = 5;
Данная запись обозначает: « Сопоставление с образцом (match)В языке конструкция match представляет собой обобщённую и усовершенствованную версию конструкции switch языка C. Более того, match является самым мощным, универсальным и, можно даже сказать, ключевым элементом управления не только потоком выполнения, но и структурами данных в языке. В выражениях match можно сопоставлять несколько шаблонов, используя синтаксис let x = 10;
match x {
1 | 2 => println!("один или два"),
3 => println!("три"),
4..=10 => println!("от четырёх до десяти"), // Отработает эта ветка, ведь 10 принадлежит данному диапазону.
_ => println!("что угодно, не соответствующее условиям выше"), // "_" соответствует любому значению
}
ДеструктуризацияПри работе с составными типами данных (структура, перечисление, кортеж, массив) можно разобрать их на части («деструктурировать») внутри шаблона. Деструктуризация структуры: struct Point {
x: i32,
y: i32,
}
let point = Point { x: 0, y: 0 };
match point {
Point { x: 0, y } => println!("x - ноль, y равен {}", y), // так как "x" равен нулю, отработает эта ветка.
Point { x, y: 0 } => println!("x равен {}, y - ноль", x),
Point { x, y } => println!("x = {}, y = {}", x, y),
}
Деструктуризация перечисления: enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
let color = Color::Hsv(0, 0, 100);
match color {
Color::Rgb(0, 0, 0) | Color::Hsv(0, 0, 0) => println!("чёрный"),
Color::Rgb(255, 255, 255) | Color::Hsv(0, 0, 100) => println!("белый"), // отработает эта ветка.
Color::Rgb(red, green, blue) => {
println!("красный: {}, зелёный: {}, синий: {}", red, green, blue)
} // отработает при любых значениях Rgb, которые не соответствуют условиям выше.
Color::Hsv(hue, saturation, brightness) => println!(
"тон: {}, насыщенность: {}, яркость: {}",
hue, saturation, brightness
), // то же самое, только с Hsv.
}
Деструктуризация кортежа: let (a, b) = (1, 2);
println!("{}", a); // 1
println!("{}", b); // 2
Условные выражения (if let)
Синтаксис let x = Some(10);
if let Some(value) = x {
// здесь мы деструктурируем x, переменная value хранит значение 10.
// выполнится эта ветка, так как "x" хранит внутри значение.
println!("значение = {}", value);
} else {
// оператор "else" здесь выступает заменой "_" в выражениях match.
println!("x - пуст");
}
unsafeВ блоках и функциях, помеченных
К Объектная системаВ Rust объектная система основана на типажах (traits) и структурах (structs). Типажи определяют сигнатуры методов, которые должны быть реализованы для каждого типа (чаще всего — структуры), реализующего типаж. Типаж может содержать и реализации методов, принимаемые по умолчанию. Реализация типажей для данной структуры, а также реализация собственных методов структуры обозначается ключевым словом Rust поддерживает аналогию наследования типажей — типаж может требовать от реализующего типа реализацию других типажей. Однако языковой поддержки наследования самих типов, и следовательно, классического ООП, в Rust нет. Вместо наследования типов, аналогия иерархии классов реализуется введением типажей, включением структуры-предка в структуру-потомка или введением перечислений для обобщения разных структур[37]. Язык поддерживает обобщённые типы (generics). Помимо функций, обобщёнными в Rust могут быть комплексные типы данных, структуры и перечисления. Компилятор Rust компилирует обобщённые функции весьма эффективно, применяя к ним мономорфизацию (генерация отдельной копии каждой обобщённой функции непосредственно в каждой точке её вызова). Таким образом, копия может быть адаптирована под конкретные типы аргументов, а следовательно, и оптимизирована для этих типов. В этом отношении обобщённые функции Rust сравнимы по производительности с шаблонами языка C++. Параллельные вычисленияВ более ранних версиях языка поддерживались легковесные потоки, но потом от них отказались в пользу нативных потоков операционной системы. При этом рекомендуемым методом обмена данными между потоками является отправка сообщений, а не использование общей памяти. Для достижения высокой производительности возможно отправлять данные не через копирование, а используя собственные указатели ( Определение и вызов асинхронных операций поддерживаются на уровне синтаксиса языка: ключевое слово Другие особенностиСистема модулей: единица компиляции («крейт») может состоять из нескольких модулей. Иерархия модулей, как правило, совпадает с иерархией каталогов и файлов проекта. Модуль (как правило) является отдельным файлом, а также является пространством имён и одним из средств управления видимостью идентификаторов: в пределах модуля (и в подмодулях) «видны» все идентификаторы, в вышестоящих модулях видны только публичные ( Автоматизированное тестирование: язык даёт возможность реализовать автоматизированные модульные тесты (юнит-тесты) прямо в тестируемом модуле либо подмодуле. Тестовые методы при компиляции игнорируются и вызываются только при тестировании. Интеграционные тесты реализуются как отдельные крейты в каталоге Автоматизированное документирование: средство rustdoc позволяет генерировать HTML-документацию прямо из исходного кода. Документация в коде маркируется тройным слешем ( Система управления пакетами: менеджер пакетов cargo (являющийся также основным инструментом создания, компиляции и тестирования проектов) с помощью файла манифеста Cargo.toml разрешает зависимости проекта (импортируемые крейты), загружая их из репозитория crates.io. Требования к идентификаторам: компилятор контролирует выполнение соглашений об именовании переменных, типов, функций и так далее (snake_case, UpperCamelCase, SCREAMING_SNAKE_CASE), а также неиспользуемые идентификаторы; неиспользуемые идентификаторы рекомендуется начинать со знака подчёркивания; есть определённые рекомендации по именованию конструкторов, методов преобразования типов и др.[40] Примерыfn main() {
println!("Hello, world!");
}
fn declension_of_noun(count: u8) -> &'static str {
let remainder = count % 10;
// исключения из правил
if count >= 11 && count <= 14 {
return "бутылок";
}
match remainder {
1 => return "бутылка",
2..=4 => return "бутылки",
_ => return "бутылок",
}
}
fn main() {
let mut word = declension_of_noun(99);
for i in (2..=99).rev() {
println!("{} {} пива на стене", i, word);
println!("{} {} пива!", i, word);
println!("Возьми одну, пусти по кругу");
word = declension_of_noun(i - 1);
println!("{} {} пива на стене!\n", i - 1, word);
}
println!("1 бутылка пива на стене");
println!("1 бутылка пива!");
println!("Возьми одну, пусти по кругу");
println!("Нет больше бутылок пива на стене!\n");
println!("Нет бутылок пива на стене!");
println!("Нет бутылок пива!");
println!("Пойди в магазин и купи ещё");
println!("99 бутылок пива на стене!");
}
Сравнение с другими языкамиПринципы работы с памятью Rust ощутимо отличаются как от языков с полным доступом к памяти, так и от языков с полным контролем за памятью со стороны сборщика мусора. Модель памяти Rust построена таким образом, что, с одной стороны, предоставляет разработчику возможность контролировать, где размещать данные, вводя разделение по типам указателей и обеспечивая контроль за их использованием на этапе компиляции. C другой стороны, механизм подсчёта ссылок Rust старается выдавать ошибки компиляции в тех случаях, в которых использование прочих языков приводит к ошибкам времени выполнения или аварийному завершению программ. Язык позволяет объявлять функции и блоки кода как «небезопасные» ( Примечания
Литература
Ссылки
|