Hello world!
Представляю вашему вниманию первую часть большой шпаргалки по Rust.
Обратите внимание: шпаргалка рассчитана на людей, которые хорошо знают любой современный язык программирования, а не на тех, кто только начинает кодить ;)
Также настоятельно рекомендуется хотя бы по диагонали прочитать замечательный Учебник по Rust (на русском языке).
Содержание
- Основы
- Комментарии и документирование кода
- Переменные, константы и статики
- Функции
- Примитивные типы данных
- Операторы
- Потоки управления
- Больше, чем основы
- Векторы (vectors)
- Структуры (structs)
- Перечисления (enums)
- Дженерики (generics)
- Реализации и трейты (impls \& traits)
- Сложная часть
- Владение (ownership)
- Заимствование (borrowing)
- Времена жизни (lifetimes)
Основы
Комментарии и документирование кода
Комментарии
// Строчные комментарии
/* Блочные комментарии */
Поддерживаются вложенные блочные комментарии.
Старайтесь избегать использования блочных комментариев.
Комментарии документации (doc comments)
Команда cargo doc
генерирует документацию проекта с помощью rustdoc. Для генерации документации используются док-комментарии.
Обычно мы добавляем док-комментарии в библиотечные крейты (library crates). Внутри док-комментариев можно использовать Markdown.
/// Строчные комментарии; документируют следующий элемент
/** Блочные комментарии; документируют следующий элемент */
//! Строчные комментарии; документируют вложенный элемент
/*! Блочные комментарии; документируют вложенный элемент !*/
Пример:
/// Этот модуль содержит тесты; внешний комментарий
mod tests {
// ...
}
mod tests {
//! Этот модуль содержит тесты; внутренний комментарий
// ...
}
Ключевое слово mod
используется для модулей. Мы обсудим это позже.
Док-атрибуты (doc attributes)
Док-атрибуты являются альтернативой док-комментариям. Мы используем их для управления rustdoc
. Подробнее о док-атрибутах можно почитать здесь.
В следующем примере каждый комментарий эквивалентен соответствующему док-атрибуту:
/// Внешний комментарий
#[doc = "Внешний комментарий"]
//! Внутренний комментарий
#![doc = "Внутренний комментарий"]
Атрибут — это общие метаданные в свободной форме, которые интерпретируются в соответствии с названием, соглашением, версиями языка и компилятора. Синтаксис:
- внешний атрибут:
#[attr]
- внутренний атрибут:
#![attr]
Перед тем, как двигаться дальше...
- Используйте
//!
только для написания документации крейта. Для блоковmod
используйте///
снаружи блоков. Взгляните на использование док-комментариев//!
и///
в популярных крейтах на crates.io. Например, взгляните на serde/src/lib.rs и rand/src/lib.rs - выполните команду
cargo new hello_lib --lib
для создания образца крейта и замените код в файлеsrc/lib.rs
на следующий:
//! Простой крейт Hello World
/// Эта функция возвращает приветствие; Hello, world!
pub fn hello() -> String {
("Hello, world!").to_string()
}
#[cfg(test)]
mod tests {
use super::hello;
#[test]
fn test_hello() {
assert_eq!(hello(), "Hello, world!");
}
}
Затем выполните cargo doc --open
для генерации документации и ее открытия в вашем дефолтном браузере.
Переменные, константы и статики
- В
Rust
переменные по умолчанию являются иммутабельными (неизменными/неизменяемыми) (immutable), поэтому они называются привязками переменных (variable bindings). Для объявления мутируемой (изменяемой) (mutable) переменной используется ключевое словоmut
. Rust
— это статически типизированный язык: типы данных (data types) проверяются во время компиляции. Однако это не означает, что типы всех переменных должны указываться явно. Компилятор "смотрит" на использование переменной и устанавливает для нее лучший тип. Но для констант (constants) и статики (statics) типы должны указываться явно. Типы указываются после двоеточие (:
).
В следующих примерах мы используем такие типы данных, как bool
, i32
, i64
и f64
. Мы обсудим это позже.
Переменные
Для объявления переменной используется ключевое слово let
. Название (имя) переменной может быть привязано к значению или функции. Также поскольку левая часть выражения привязки является "паттерном" (pattern) мы можем привязывать несколько названий к нескольким значениям или функциям.
// Иммутабельная переменная
let a; // Объявление (declaration); без типа данных
a = 5; // Присвоение/присваивание значения (assignment)
let b: i8; // Объявление; с типом данных
b = 5;
let t = true; // Объявление + присвоение; без типа данных
let f: bool = false; // Объявление + присвоение; с типом данных
// Несколько переменных
let (x, y) = (1, 2); // x = 1 и y = 2
// Мутабельная переменная
let mut z = 5;
z = 6;
// Значением переменной становится результат выражения
let z = { x + y }; // z = 3
// Затенение/перезапись переменной (variable shadowing) (см. ниже)
let z = {
let x = 1;
let y = 2;
x + y
}; // z = 3
В Rust
точка с запятой (;
) в конце строки кода является обязательной (в отличие, например, от JavaScript
). Отсутствие ;
обычно означает возврат результата выражения.
Константы
Для определения констант используется ключевое слово const
. Константы являются иммутабельными. Они живут в течение всего жизненного цикла программы, но не имеют фиксированного адреса в памяти.
// Название в стиле SCREAMING_SNAKE_CASE
// Тип указывается явно
const CONST_VAR: i32 = 5;
Статики
Ключевое слово static
используется для определения объекта типа "глобальная переменная". Для каждого значения существует только один экземпляр такого объекта. Значение находится в фиксированном месте в памяти.
static STATIC_VAR: i32 = 5;
Старайтесь всегда использовать const
вместо static
для определения констант. Привязка места в памяти к константе требуется очень редко. Использование const
позволяет выполнять такие оптимизации, как распространение константы (constant propagation) не только в вашем крейте, но также в зависимых/подчиненных крейтах.
Затенение переменных (variable shadowing)
Иногда возникает необходимость преобразовать значение переменной из одних единиц в другие для дальнейшей обработки. Rust
позволяет повторно объявлять переменные с другими типами данных и/или другими настройками мутабельности. Это называется затенением.
fn main() {
let x: f64 = -20.48; // float - число с плавающей точкой
// Явное приведение/преобразование типа
let x: i64 = x.floor() as i64; // int - целое число
println!("{}", x); // -21
let s: &str = "hello"; // &str - строковый срез
// Неявное преобразование типа
let s: String = s.to_uppercase(); // String - строка
println!("{}", s) // HELLO
}
Перед тем, как двигаться дальше...
- Для названий переменных используется стиль snake_case, а для названий констант и статик — SCREAMING_SNAKE_CASE
- обычно константы и статики определяются в начале файла снаружи функции (после импорта модулей/объявлений
use
)
const PI: f64 = 3.14159265359;
fn main() {
println!("Значение π: {}", PI);
}
Функции
Именованные функции
- объявляются с помощью ключевого слова
fn
- при использовании аргументов, необходимо определять их типы
- по умолчанию функции возвращают пустой кортеж (tuple) (
()
). Возвращаемый тип определяется после->
Hello world
fn main() {
println!("Hello, world!");
}
Передача аргументов
fn print_sum(a: i8, b: i8) {
println!("Cумма: {}", a + b);
}
Возврат значения
// Без ключевого слова `return`. Возвращается только последнее выражение
fn plus_one(a: i32) -> i32 {
a + 1
// В конце этой строки отсутствует `;`
// Это выражение эквивалентно `return a + 1;`
}
// С ключевым словом `return`
fn plus_two(a: i32) -> i32 {
return a + 2;
// Ключевое слово `return` следует использовать только для условного/раннего возврата (early return).
// Использование `return` в последнем выражении считается плохой практикой
}
Указатели на функцию (function pointers), использование в качестве типа данных
fn main() {
// Без объявлений типов
let p1 = plus_one;
let x = p1(5); // 6
// С объявлениями типов
let p1: fn(i32) -> i32 = plus_one;
let x = p1(5); // 6
}
fn plus_one(a: i32) -> i32 {
a + 1
}
Замыкания (closures)
- Также известны как анонимные или лямбда-функции
- типы аргументов и возвращаемого значения являются опциональными
Именованная функция без замыкания
fn main() {
let x = 2;
println!("{}", get_square_value(x));
}
fn get_square_value(i: i32) -> i32 {
i * i
}
С опциональными объявлениями типов аргумента и возвращаемого значения
fn main() {
let x = 2;
// Аргументы передаются внутри | |, а тело выражения оборачивается в { }
let square = |i: i32| -> i32 {
i * i
};
println!("{}", square(x));
}
Без объявлений типов
fn main() {
let x = 2;
let square = |i| i * i; // { } являются опциональными для однострочных замыканий
println!("{}", square(x));
}
С опциональными объявлениями типов; создание + вызов
fn main() {
let x = 2;
let x_square = |i: i32| -> i32 { i * i }(x); // { } являются обязательными при одновременном создании и вызове
println!("{}", x_square);
}
Без объявлений типов; создание + вызов
fn main() {
let x = 2;
let x_square = |i| -> i32 { i * i }(x); // тип возвращаемого значения является обязательным
println!("{}", x_square);
}
Примитивные типы данных
bool
true
или false
.
let x = true;
let y: bool = false;
char
Единичное скалярное значение Юникода.
// Кавычки должны быть одинарными
let x = 'x';
let y: char = '