
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 = ' 