Главная / Курсы / Rust / Глава 1. Ключевые факты
# Глава 1. Ключевые факты > The most loved programming language По версии ежегодного опроса разработчиков Stack Overflow Developer Survey с 2016 по 2023 год. Глава посвящается ключевым фактам о языке программирования Rust. Она позволит составить первое впечатление о языке, узнать его плюсы и минусы. ## Что такое Rust [Rust](https://www.rust-lang.org/) — это компилируемый язык, поддерживающий множество парадигм программирования. Rust позволяет писать программы в функциональном и императивном стилях. Наследование типов в языке отсутствует, то есть классического ООП в Rust нет. Язык поддерживает обобщенные типы данных (generics). Обобщенными в Rust могут быть функции, структуры и перечисления. При создании языка ключевыми приоритетами были выбраны: - безопасность, - скорость, - параллелизм. ## Типизация Безопасность в языке Rust обеспечивается строгой статической типизацией. **Статическая типизация** определяет правило связывания типа с сущностями программы. При статической типизации переменная, параметр функции, возвращаемое значение функции связывается с типом в момент объявления и этот тип не может быть изменен позже. В этом примере объявлена целочисленная переменная `k` со значением 10, при помощи ключевого слова `mut` указывается, что она изменяемая: ```rust let mut k = 10; ``` Здесь `k` присваивается новое значение -20. ```rust k = -20; ``` По умолчанию целочисленные литералы имеют тип `i32`, 32-х битное знаковое целое. Поэтому переменная `k` также будет иметь тип `i32`, для которого значение -20 является допустимым. Однако попытка присвоить `k` строковый литерал приведет к ошибке компиляции: ``` error[E0308]: mismatched types --> src\main.rs:3:9 | 2 | let mut k = 10; | -- expected due to this value 3 | k = "3"; | ^^^ expected integer, found `&str` ``` Нельзя изменить тип переменной `k` с целочисленного на строчный и присвоить `k` строку. **Строгая (сильная)** типизация — это политика работы с типами, при которой не допускается смешивание сущностей разных типов в выражениях и не выполняются никакие автоматические преобразования. Функция `calc()` возвращает значение входного аргумента, увеличенное на 10. Тип входного параметра и возвращаемого значения функции — 64-х битное знаковое целое. Для возврата значения из функции ключевое слово `return` можно опустить. ```rust fn calc (v: i64) -> i64 { v + 10 } ``` Здесь в функцию передается 64-х битное знаковое целое: ```rust let value: i64 = 42; let result = calc(value); ``` Код скомпилируется без ошибок, тип входного параметра соответствует определенному в функции. Но если проделать то же самое с 32-х битным знаковым целым, то ничего не выйдет: ``` error[E0308]: mismatched types --> src\main.rs:7:23 | 7 | let result = calc(value); | ---- ^^^^^ expected `i64`, found `i32` | | | arguments to this function are incorrect | note: function defined here --> src\main.rs:1:4 | 1 | fn calc (v: i64) -> i64 { | ^^^^ ------ help: you can convert an `i32` to an `i64` | 7 | let result = calc(value.into()); | +++++++ ``` Наверное, это неожиданно, но компиляция завершается с ошибкой! Неявное преобразование из `i32` в `i64` недопустимо. Для таких случаев Rust требует явного преобразования типов: ```rust let result = calc(value as i64); // или let result = calc(value.into()); // или let result = calc(i64::from(value)); ``` В Rust используется **явная** типизация, то есть при объявлении переменной требуется указывать ее тип: ```rust // 32-х битное беззнаковое целое let index: u32 = 100; // переменная булевого типа let flag: bool = false; ``` Можно опустить явное указание типа в тех случаях, когда компилятор способен вычислить тип переменной из инициализирующего выражения. ```rust // 32-х битное беззнаковое целое, явное указание типа let val: u32 = 100; // тип u32 определяется инициализирующим выражением let double_val = 2 * val; ``` ## Безопасная работа с памятью Rust гарантирует безопасную работу с памятью благодаря встроенной в компилятор системе статической проверки ссылок (borrow checker). Это обеспечивается строгим выполнением правил **владения** (ownership) и **заимствования** (borrowing). Правила **владения** подразумевают, что у каждого значения есть один и только один владелец в каждый момент времени. Правила **заимствования** реализуют семантику *"либо много читателей, либо один писатель"*. Это означает, что для каждой переменной может существовать либо несколько заимствований (ссылок) на чтение, либо только одна на запись. Время жизни заимствования не может быть больше времени жизни заимствованного объекта. Кроме того, обеспечивается контроль над использованием неинициализированных и деинициализированных переменных. Все проверки корректности выполнения правил владения и заимствования происходят во время компиляции. Если какое-то правило будет нарушено, то программа на Rust просто не скомпилируется. Cтрогое выполнение правил владения и заимствования позволяет избежать ситуации, когда код программы обращается к удаленному либо не аллоцированному участку памяти (segmentation fault). Это проблема, хорошо знакомая C и C++ разработчикам. Кроме того, в Rust предусмотрена поддержка параллельного программирования с предотвращением гонки данных. Здесь также работают вышеописанные правила. ## Производительность Rust создавался, в том числе как язык системного программирования. А это подразумевает высокую производительность и рачительное использование доступных ресурсов. Высокой производительности программ на Rust способствует использование *абстракций с нулевой стоимостью*. Так, статическая проверка ссылок во время компиляции не порождает дополнительного исполняемого кода и не вызывает дополнительных накладных расходов во время работы программы. В дополнение к этому в Rust поддерживается *move-семантика*. Это означает, что объект, расположенный на куче, при присваивании перемещается к новому владельцу (переменной), делая старого владельца недействительным (деинициализированная переменная). Например, если создать строковую переменную `s1` и присвоить ее значение `s2`, то произойдет перемещение значения, а не копирование: ```rust let s1 = String::from("moved value"); let s2 = s1; ``` Значение "moved value" будет перемещено к новому владельцу — `s2`. При этом `s1` станет деинициализированной и недоступной к использованию. Попытка распечатать значение `s1` приведет к ошибке компиляции: ``` error[E0382]: borrow of moved value: `s1` --> src\main.rs:4:20 | 2 | let s1 = String::from("moved value"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 3 | let s2 = s1; | -- value moved here 4 | println!("{}", s1); | ^^ value borrowed here after move | = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider cloning the value if the performance cost is acceptable | 3 | let s2 = s1.clone(); | ++++++++ ``` Значение переменной `s1` недоступно, но можно вывести на консоль значение `s2`: ```rust println!("{}", s2); ``` Будет напечатано: `moved value`. Использование move-семантики позволяет избежать лишнего копирования и дополнительных расходов во время исполнения программы. При этом есть возможность скопировать значение без перемещения. Для копирования (клонирования) значения нужно воспользоваться методом `clone()`: ```rust let s1 = String::from("copied value"); let s2 = s1.clone(); println!("s1 = '{}', s2 = '{}'", s1, s2); ``` После клонирования обе переменные доступны, в консоль будет выведено: `s1 = 'copied value', s2 = 'copied value'`. Для поддержки низкоуровневого программирования Rust разрешает объявлять функции и блоки кода как *небезопасные* (unsafe). Для небезопасного кода отключаются некоторые ограничения, что позволяет выполнять операции на более низком уровне. Но разработчик должен полностью понимать, что он делает. Для большинства задач `unsafe` не требуется и не рекомендуется к использованию. Иногда `unsafe` необходим, например, для подключения библиотек, написанных на языке C. ## Метапрограммирование Метапрограммирование — это создание программ, которые в результате своей работы порождают другие программы. Основной целью метапрограммирования является уменьшение кода, который должен написать и поддерживать программист. В Rust есть мощный инструмент для метапрограммирования — макросы. Одна строка макроса легко заменит сотни строк рутинного (boilerplate) кода. Макросы, похожие на функции, выглядят подобно вызову функций, но всегда заканчиваются на `!`. Используемый в примерах макрос `println!` выводит на консоль отформатированную строку. Форматирование строк в Rust похоже на форматирование строк в Python, C# и C++20: ```rust let val = 11; print!("the value is {val}"); let zero = 0; println!(" and it is greater than {}\n", zero); let arr = ['a', 'r', 'r', 'a', 'y']; println!("{:?}", arr); println!("symbols 2..4: \"{2}{1}{0}\"", arr[4], arr[3], arr[2]); ``` Будет напечатано: ``` the value is 11 and it is greater than 0 ['a', 'r', 'r', 'a', 'y'] symbols 2..4: "ray" ``` Макрос `print!` аналогичен `println!`, только не добавляет символ переноса строки при форматировании. Во время компиляции макросы раскрываются и подставляются в абстрактное синтаксическое дерево (AST) программы. Создатели Rust постарались сделать сообщения об ошибках в макросах понятными и информативными для разработчика. ```rust let val = 11; print!("the value is {vla}"); ``` В макросе название переменной `val` содержит опечатку. Компилятор сообщит об ошибке и предложит возможное исправление: ``` error[E0425]: cannot find value `vla` in this scope --> src\main.rs:3:27 | 3 | print!("the value is {vla}"); | ^^^ help: a local variable with a similar name exists: `val` ``` Макросы в Rust — очень мощный инструмент. В том числе язык поддерживает возможность написания пользовательских макросов. Мы рассмотрим ее в одной из следующих глав. Вряд ли новичку потребуется писать свои макросы, но вот их использование сильно облегчает жизнь программиста. ## Заключение - Rust — это компилируемый язык, позволяющий писать программы в функциональном и императивном стилях. Однако классического ООП в языке нет. - Ключевыми приоритетами языка являются: безопасность, скорость и параллелизм. - Безопасность в Rust основывается на строгой статической типизации и управлении памятью. - Безопасная работа с памятью гарантируется системой статической проверки ссылок, обеспечивающей строгое выполнение правил владения и заимствования. - Правила владения и заимствования подразумевают, что у каждого значения есть один владелец, а одновременно может быть несколько читателей либо один писатель. - Высокая производительность программ на Rust обеспечивается за счет компилируемости и использования абстракций с нулевой стоимостью. - Обобщенное и метапрограммирование помогают разработчикам Rust сократить размер рутинного (boilerplate) кода.
Отправка...

Если вам нравится проект, вы можете поддержать его!

Задонатить