LEARN X · ЗА 20 МИН
Rust
Экспресс-тур по Rust за 20 минут: владение, заимствование, типы, match, Result, трейты и дженерики — весь язык в комментариях кода.
Rust — системный язык с безопасностью памяти без сборщика мусора. Его суперсила — модель владения, которая отлавливает ошибки работы с памятью на этапе компиляции. Ниже весь язык на одной странице: читай комментарии в коде.
1. Структура программы
// Однострочный комментарий
/* Многострочный
комментарий */
// Точка входа в программу — функция main
fn main() {
// println! — это макрос (восклицательный знак), а не функция
println!("Привет, Rust!"); // вывод: Привет, Rust!
// {} — плейсхолдер для подстановки значения
println!("2 + 2 = {}", 2 + 2); // вывод: 2 + 2 = 4
// print! — без перевода строки
print!("без");
print!("переноса\n"); // вывод: безпереноса
}
// Компиляция: rustc main.rs && ./main
// Через Cargo: cargo run
2. Переменные и изменяемость
fn main() {
// По умолчанию переменные НЕИЗМЕНЯЕМЫ (immutable)
let x = 5;
// x = 6; // ОШИБКА компиляции: cannot assign twice to immutable variable
// mut делает переменную изменяемой
let mut y = 10;
y = 20; // ок
println!("y = {}", y); // y = 20
// Явное указание типа через двоеточие
let age: u32 = 30;
println!("age = {}", age); // age = 30
// const — константа, тип обязателен, вычисляется на этапе компиляции
const MAX_POINTS: u32 = 100_000; // _ можно для читаемости
println!("max = {}", MAX_POINTS); // max = 100000
// Затенение (shadowing) — новая переменная с тем же именем
let z = 5;
let z = z + 1; // z = 6
let z = z * 2; // z = 12
let z = "теперь строка"; // можно даже сменить тип!
println!("z = {}", z); // z = теперь строка
}
3. Базовые типы и строки
fn main() {
// Целые: i8 i16 i32 i64 i128 (знаковые), u8..u128 (беззнаковые)
let a: i32 = -42;
let b: u8 = 255;
// Плавающая точка: f32, f64 (по умолчанию f64)
let pi: f64 = 3.14159;
// Булев тип
let flag: bool = true;
// Символ char — это ОДИН Unicode-символ (4 байта), кавычки одинарные
let letter: char = 'Я';
let emoji: char = '🦀';
println!("{} {} {} {} {} {}", a, b, pi, flag, letter, emoji);
// вывод: -42 255 3.14159 true Я 🦀
// Две строковые сущности:
// &str — строковый срез (string slice), неизменяемая ссылка
let greeting: &str = "Привет";
// String — растущая строка, владеет данными в куче
let mut owned: String = String::from("Привет");
owned.push_str(", мир"); // можно дополнять
println!("{}", owned); // Привет, мир
// &str -> String и обратно
let s: String = greeting.to_string();
let back: &str = &s; // ссылка на String -> &str
println!("{} {}", s, back); // Привет Привет
}
4. Операторы и условия
fn main() {
// Арифметика: + - * / %
println!("{}", 7 / 2); // 3 (целочисленное деление)
println!("{}", 7 % 2); // 1 (остаток)
println!("{}", 7.0 / 2.0); // 3.5
// Логические: && || ! Сравнение: == != < > <= >=
let n = 5;
// if/else — БЕЗ скобок вокруг условия
if n < 0 {
println!("отрицательное");
} else if n == 0 {
println!("ноль");
} else {
println!("положительное"); // сюда
}
// if — это ВЫРАЖЕНИЕ, его можно присвоить (аналог тернарника)
let parity = if n % 2 == 0 { "чёт" } else { "нечёт" };
println!("{}", parity); // нечёт
// match — мощный switch (подробнее в секции 11)
let grade = match n {
0 => "ноль",
1..=4 => "мало", // диапазон включительно
5 | 6 | 7 => "средне", // несколько значений
_ => "много", // _ — все остальные случаи
};
println!("{}", grade); // средне
}
5. Циклы
fn main() {
// loop — бесконечный цикл, выход через break
let mut i = 0;
loop {
i += 1;
if i == 3 { break; }
}
println!("i = {}", i); // i = 3
// loop может ВОЗВРАЩАТЬ значение через break
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 { break counter * 2; }
};
println!("result = {}", result); // result = 20
// while — пока условие истинно
let mut n = 3;
while n > 0 {
print!("{} ", n); // 3 2 1
n -= 1;
}
println!();
// for по диапазону: 1..4 — это 1,2,3 (правая граница НЕ входит)
for x in 1..4 {
print!("{} ", x); // 1 2 3
}
println!();
// 1..=4 — включительно: 1,2,3,4
for x in 1..=4 {
print!("{} ", x); // 1 2 3 4
}
println!();
// for по коллекции
let arr = [10, 20, 30];
for v in arr.iter() {
print!("{} ", v); // 10 20 30
}
println!();
}
6. Кортежи и массивы
fn main() {
// Кортеж (tuple) — фиксированный набор разных типов
let person: (&str, i32, bool) = ("Аня", 25, true);
// Доступ по индексу через точку
println!("{} {} {}", person.0, person.1, person.2);
// вывод: Аня 25 true
// Деструктуризация кортежа
let (name, age, active) = person;
println!("{} {} {}", name, age, active); // Аня 25 true
// Массив [тип; длина] — фиксированный размер, один тип
let nums: [i32; 5] = [1, 2, 3, 4, 5];
println!("{}", nums[0]); // 1
println!("{}", nums.len()); // 5
// Массив из повторяющихся значений
let zeros = [0; 3]; // [0, 0, 0]
println!("{:?}", zeros); // {:?} — отладочный вывод: [0, 0, 0]
// Срез (slice) — ссылка на часть массива, &arr[start..end]
let middle = &nums[1..4]; // элементы с индексами 1,2,3
println!("{:?}", middle); // [2, 3, 4]
}
7. Векторы и коллекции
use std::collections::HashMap;
fn main() {
// Vec<T> — динамический массив (растущий)
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
v.push(3);
println!("{:?}", v); // [1, 2, 3]
// Макрос vec! для быстрого создания
let mut nums = vec![10, 20, 30];
nums.push(40);
println!("len = {}", nums.len()); // len = 4
// Доступ: индекс (паника при выходе за границы) или .get() -> Option
println!("{}", nums[0]); // 10
println!("{:?}", nums.get(99)); // None (безопасно)
// HashMap<K, V> — словарь ключ-значение
let mut scores: HashMap<&str, i32> = HashMap::new();
scores.insert("Аня", 95);
scores.insert("Боб", 80);
// Чтение возвращает Option<&V>
if let Some(score) = scores.get("Аня") {
println!("Аня: {}", score); // Аня: 95
}
// Перебор пар
for (name, score) in &scores {
println!("{} => {}", name, score); // порядок не гарантирован
}
}
8. Функции
// Функция с параметрами и возвращаемым типом (-> тип)
fn add(a: i32, b: i32) -> i32 {
// Последнее ВЫРАЖЕНИЕ без точки с запятой — возвращаемое значение
a + b // НЕТ ; — это return
}
// Явный return тоже работает (для раннего выхода)
fn abs(x: i32) -> i32 {
if x < 0 {
return -x; // ранний возврат
}
x
}
// Без -> функция возвращает () — "unit", пустой кортеж (как void)
fn greet(name: &str) {
println!("Привет, {}!", name);
}
fn main() {
println!("{}", add(2, 3)); // 5
println!("{}", abs(-7)); // 7
greet("Rust"); // Привет, Rust!
// Блок { } — тоже выражение, возвращает последнюю строку
let y = {
let t = 5;
t * t // значение блока
};
println!("{}", y); // 25
}
9. Владение и заимствование
Главная фишка Rust. У каждого значения есть один владелец. Когда владелец выходит из области видимости — память освобождается. Это даёт безопасность без сборщика мусора.
fn main() {
// ВЛАДЕНИЕ (ownership)
let s1 = String::from("привет");
let s2 = s1; // s1 ПЕРЕМЕЩЁН (move) в s2, s1 больше не валиден
// println!("{}", s1); // ОШИБКА: value borrowed after move
println!("{}", s2); // привет — ок
// Чтобы скопировать данные, а не переместить — .clone()
let a = String::from("мир");
let b = a.clone(); // глубокая копия
println!("{} {}", a, b); // мир мир — оба валидны
// Простые типы (i32, bool, char...) КОПИРУЮТСЯ, а не перемещаются
let x = 5;
let y = x; // копия
println!("{} {}", x, y); // 5 5 — оба валидны
// ЗАИМСТВОВАНИЕ (borrowing) — даём ссылку &, не передавая владение
let text = String::from("Rust");
let length = calc_len(&text); // передаём ССЫЛКУ
println!("'{}' длина {}", text, length); // text всё ещё валиден!
// вывод: 'Rust' длина 4
// Изменяемое заимствование &mut — только ОДНО одновременно
let mut msg = String::from("привет");
add_excl(&mut msg);
println!("{}", msg); // привет!
}
// &String — заимствуем, владение остаётся у вызывающего
fn calc_len(s: &String) -> usize {
s.len()
} // s выходит из области, но данные НЕ освобождаются (мы не владели)
// &mut — изменяемая ссылка
fn add_excl(s: &mut String) {
s.push('!');
}
10. Структуры и перечисления
// struct — пользовательский тип с именованными полями
struct User {
name: String,
age: u32,
active: bool,
}
// impl — блок реализации методов для типа
impl User {
// Ассоциированная функция (как конструктор), без self
fn new(name: &str, age: u32) -> User {
User { name: name.to_string(), age, active: true }
}
// Метод — первый параметр &self (ссылка на экземпляр)
fn greet(&self) -> String {
format!("Я {}, мне {}", self.name, self.age)
}
// &mut self — метод, изменяющий экземпляр
fn deactivate(&mut self) {
self.active = false;
}
}
// enum — перечисление, может нести данные в вариантах
enum Shape {
Circle(f64), // радиус
Rect(f64, f64), // ширина, высота
Point, // без данных
}
fn area(s: &Shape) -> f64 {
match s {
Shape::Circle(r) => 3.14159 * r * r,
Shape::Rect(w, h) => w * h,
Shape::Point => 0.0,
}
}
fn main() {
let mut u = User::new("Аня", 25);
println!("{}", u.greet()); // Я Аня, мне 25
u.deactivate();
println!("active = {}", u.active); // active = false
let c = Shape::Circle(2.0);
let r = Shape::Rect(3.0, 4.0);
println!("{}", area(&c)); // 12.56636
println!("{}", area(&r)); // 12
}
11. Сопоставление с образцом
Option<T> заменяет null: либо Some(значение), либо None. Компилятор заставляет обработать оба случая.
fn main() {
// Option<T> — значение есть (Some) или нет (None)
let some_num: Option<i32> = Some(7);
let no_num: Option<i32> = None;
// match обязан покрыть ВСЕ варианты
match some_num {
Some(n) => println!("есть число {}", n), // есть число 7
None => println!("ничего"),
}
// if let — короткая запись, когда интересен один случай
if let Some(n) = some_num {
println!("n = {}", n); // n = 7
}
// unwrap_or — значение или дефолт, если None
println!("{}", no_num.unwrap_or(0)); // 0
// Сложный match с условиями (guard) и привязкой
let pair = (0, 5);
match pair {
(0, y) => println!("на оси Y, y={}", y), // сюда: на оси Y, y=5
(x, 0) => println!("на оси X, x={}", x),
(x, y) if x == y => println!("диагональ"),
_ => println!("где-то ещё"),
}
}
12. Обработка ошибок
Result<T, E> — успех Ok(значение) или ошибка Err(причина). Оператор ? пробрасывает ошибку наверх.
// Функция возвращает Result: успех или ошибку
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("деление на ноль"))
} else {
Ok(a / b)
}
}
// Оператор ? : если Ok — достаёт значение, если Err — сразу return Err
fn calc() -> Result<f64, String> {
let x = divide(10.0, 2.0)?; // x = 5.0
let y = divide(x, 0.0)?; // здесь вернётся Err и функция прервётся
Ok(y)
}
fn main() {
// Обработка через match
match divide(10.0, 2.0) {
Ok(v) => println!("= {}", v), // = 5
Err(e) => println!("ошибка: {}", e),
}
match calc() {
Ok(v) => println!("= {}", v),
Err(e) => println!("ошибка: {}", e), // ошибка: деление на ноль
}
// panic! — аварийное завершение программы (только для непоправимого)
let v = vec![1, 2, 3];
// v[99]; // вызвал бы panic: index out of bounds
println!("len = {}", v.len()); // len = 3
// panic!("всё сломалось"); // принудительная паника
}
13. Трейты и дженерики
Трейт (trait) — общее поведение (как интерфейс). Дженерики <T> — код для любого типа.
// Дженерик-функция: работает с любым типом T,
// который умеет сравниваться (ограничение PartialOrd)
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut max = list[0];
for &item in list.iter() {
if item > max { max = item; }
}
max
}
// trait — описание общего поведения
trait Describe {
fn describe(&self) -> String;
// Можно дать реализацию по умолчанию
fn shout(&self) -> String {
format!("{}!!!", self.describe())
}
}
struct Dog;
struct Cat;
// Реализуем трейт для конкретных типов
impl Describe for Dog {
fn describe(&self) -> String { String::from("Гав") }
}
impl Describe for Cat {
fn describe(&self) -> String { String::from("Мяу") }
}
// Дженерик-структура: поле любого типа T
struct Wrapper<T> {
value: T,
}
fn main() {
let nums = vec![3, 7, 2, 9, 4];
println!("max = {}", largest(&nums)); // max = 9
let chars = vec!['a', 'z', 'm'];
println!("max = {}", largest(&chars)); // max = z
let d = Dog;
let c = Cat;
println!("{}", d.describe()); // Гав
println!("{}", c.shout()); // Мяу!!! (метод по умолчанию)
let w = Wrapper { value: 42 };
println!("{}", w.value); // 42
}