Skip to content

Latest commit

 

History

History
2045 lines (1574 loc) · 56.2 KB

RUST.md

File metadata and controls

2045 lines (1574 loc) · 56.2 KB

Полчаса на изучение Rust

27 января 2020 года. 21 минута чтения

Содержание

  • Привязки переменных
    • Ключевое слово let
    • Аннотация типа
    • Неинициализированные переменные
    • Отбрасывание значений
    • Затенение привязок
  • Кортежи
    • Деструктуризация кортежей
  • Операторы и выражения
    • Блоки
    • Неявный возврат
    • Доступ к полям и вызов методов
  • Модули и синтаксис use
    • Пространства имен типов
    • Прелюдия libstd
  • Структуры
    • Синтаксис обновления структур
    • Деструктуризация структур
  • Шаблоны и деструктуризация
    • Деструктуризация с if let
    • match как шаблон
    • Исчерпывающие match
  • Методы
    • Неизменяемость
  • Трейты
    • Правила сиротства
    • Тип Self
    • Маркерные трейты
    • Получатели методов трейтов
    • Автоматическое создание трейтов
  • Дженерики
    • Дженерик-функции
    • Ограничения параметров типа (границы трейтов)
    • Мономорфизация
    • Дженерик-структуры
    • Пример: Vec
  • Макросы
    • Макрос panic!
    • Функции, вызывающие панику
  • Перечисления (суммарные типы)
  • Время жизни
    • Правила заимствования (одно или несколько неизменяемых заимствований XOR одно изменяемое заимствование)
    • Функции с обобщенными параметрами времени жизни
    • Опущение времени жизни
    • Структуры с обобщенным временем жизни
    • Реализации с обобщенным временем жизни
    • Время жизни 'static
  • Собственные и ссылочные типы
    • Срезы
    • Перегрузка операторов
    • Правила заимствования и срезы
    • Срезы строк (&str)
    • Ошибочные функции (Result)
  • Разыменование
  • Типы функций, замыкания
    • FnMut и правила заимствования
    • Замыкание туалетного типа
    • Циклы, итераторы
    • Возвращение замыканий
    • Захват в замыкания

Привязки переменных

Ключевое слово let

let вводит привязку переменной:

let x; // объявление "x"
x = 42; // присвоение 42 "x"

Это также можно записать в одну строку:

let x = 42;

Аннотация типа

Вы можете явно указать тип переменной с помощью :, это называется аннотацией типа:

let x: i32; // `i32` - это знаковое 32-битное целое число
x = 42;

// существуют также i8, i16, i32, i64, i128
// и также u8, u16, u32, u64, u128 для беззнаковых

Это также можно записать в одну строку:

let x: i32 = 42;

Неинициализированные переменные

Если вы объявляете имя и инициализируете его позже, компилятор не позволит вам использовать его до инициализации.

let x;
foobar(x); // ошибка: заимствование возможно неинициализированной переменной: `x`
x = 42;

Однако, так делать можно:

let x;
x = 42;
foobar(x); // тип `x` будет выведен отсюда

Отбрасывание значений

Подчеркивание _ - это особое имя - точнее, "отсутствие имени". Оно означает, что что-то нужно выбросить:

// это делает *ничего*, потому что 42 - это константа
let _ = 42;

// это вызывает `get_thing`, но выбрасывает его результат
let _ = get_thing();

Имена, начинающиеся с подчеркивания, являются обычными именами, но компилятор не будет предупреждать о их неиспользовании:

// возможно, мы будем использовать `_x` позже, но наш код еще не завершен
// и мы просто хотели избавиться от предупреждения компилятора на данный момент.
let _x = 42;

Затенение привязок

Можно вводить отдельные привязки с одинаковым именем - можно затенять привязку переменной:

let x = 13;
let x = x + 3;
// использование `x` после этой строки относится только ко второму `x`,
// хотя первый `x` все еще существует (он будет удален при выходе из области видимости),
// вы больше не можете к нему обращаться.

Кортежи

Rust имеет кортежи, которые можно рассматривать как "фиксированной длины коллекции значений разных типов".

let pair = ('a', 17);
pair.0; // это 'a'
pair.1; // это 17

Если мы действительно хотим аннотировать тип пары, мы бы написали:

let pair: (char, i32) = ('a', 17);

Деструктуризация кортежей

Кортежи можно деструктурировать при присвоении, что означает их разбиение на отдельные поля:

let (some_char, some_int) = ('a', 17);
// теперь `some_char` это 'a', а `some_int` это 17

Это особенно полезно, когда функция возвращает кортеж:

let (left, right) = slice.split_at(middle);

Конечно, при деструктуризации кортежа можно использовать _, чтобы отбросить его часть:

let (_, right) = slice.split_at(middle);

Операторы и выражения

Блоки

Блоки обрамляются фигурными скобками и имеют свою собственную область видимости:

// Это печатает "in", затем "out"
fn main() {
    let x = "out";

    {
        // это другой `x`
        let x = "in";
        println!("{}", x);
    }
    println!("{}", x);
}

Блоки как выражения

Блоки также являются выражениями, что означает, что они вычисляются в значение.

// это:
let x = 42;

// эквивалентно этому:
let x = { 42 };

Внутри блока могут быть несколько операторов:

let x = {
    let y = 1; // первый оператор
    let z = 2; // второй оператор
    y + z // это *хвост* - то, во что будет вычисляться весь блок
};

Неявный возврат

Из-за этого "опускание точки с запятой в конце функции" эквивалентно возврату значения, то есть эти функции эквивалентны:

fn fair_dice_roll() -> i32 {
    return 4;
}

fn fair_dice_roll() -> i32 {
    4
}

Все является выражением

if условные выражения также являются выражениями:

fn fair_dice_roll() -> i32 {
    if feeling_lucky {
        6
    } else {
        4
    }
}

match также является выражением:

fn fair_dice_roll() -> i32 {
    match feeling_lucky {
        true => 6,
        false => 4,
    }
}

Доступ к полям и вызов методов

Точки обычно используются для доступа к полям значения:

let a = (10, 20);
a.0; // это 10

let amos = get_some_struct();
amos.nickname; // это "fasterthanlime"

Или для вызова метода на значении:

let nick = "fasterthanlime";
nick.len(); // это 14

Модули и синтаксис use

Двойное двоеточие :: аналогично, но оно работает с пространствами имен.

В этом примере, std - это crate (~ библиотека), cmp - это модуль (~ исходный файл), а min - это функция:

let least = std::cmp::min(3, 8); // это 3

Директивы use могут использоваться для "приведения в область видимости" имен из других пространств имен:

use std::cmp::min;

let least = min(7, 1); // это 1

В пределах дир

ектив use фигурные скобки имеют другое значение: они являются "глобами". Если мы хотим импортировать и min, и max, мы можем сделать любое из этих:

// это работает:
use std::cmp::min;
use std::cmp::max;

// это тоже работает:
use std::cmp::{min, max};

// это тоже работает!
use std::{cmp::min, cmp::max};

Подстановочный знак (*) позволяет импортировать каждый символ из пространства имен:

// это привносит `min` и `max` в область видимости, и многие другие вещи
use std::cmp::*;

Пространства имен типов

Типы также являются пространствами имен, и методы могут быть вызваны как обычные функции:

let x = "amos".len(); // это 4
let x = str::len("amos"); // это тоже 4

Прелюдия libstd

str - это примитивный тип, но многие непримитивные типы также находятся в области видимости по умолчанию.

// `Vec` - это обычная структура, а не примитивный тип
let v = Vec::new();

// это точно такой же код, но с *полным* путем к `Vec`
let v = std::vec::Vec::new();

Это работает, потому что Rust вставляет это в начале каждого модуля:

use std::prelude::v1::*;

(Что, в свою очередь, повторно экспортирует множество символов, таких как Vec, String, Option и Result).

Структуры

Структуры объявляются с помощью ключевого слова struct:

struct Vec2 {
    x: f64, // 64-битное число с плавающей точкой, также известное как "двойная точность"
    y: f64,
}

Их можно инициализировать, используя литералы структур:

let v1 = Vec2 { x: 1.0, y: 3.0 };
let v2 = Vec2 { y: 2.0, x: 4.0 };
// порядок не имеет значения, только имена

Синтаксис обновления структур

Существует сокращение для инициализации остальных полей из другой структуры:

let v3 = Vec2 {
    x: 14.0,
    ..v2
};

Это называется "синтаксис обновления структур", может быть только в последней позиции и не может следовать за запятой.

Обратите внимание, что остальные поля могут означать все поля:

let v4 = Vec2 { ..v3 };

Деструктуризация структур

Структуры, как и кортежи, могут быть деструктурированы.

Так же, как это является допустимым шаблоном let:

let (left, right) = slice.split_at(middle);

Так и это:

let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
// `x` теперь 3.0, `y` теперь 6.0

И это:

let Vec2 { x, .. } = v;
// это выбрасывает `v.y`

Шаблоны и деструктуризация

Деструктуризация с if let

Шаблоны let могут использоваться как условия в if:

struct Number {
    odd: bool,
    value: i32,
}

fn main() {
    let one = Number { odd: true, value: 1 };
    let two = Number { odd: false, value: 2 };
    print_number(one);
    print_number(two);
}

fn print_number(n: Number) {
    if let Number { odd: true, value } = n {
        println!("Odd number: {}", value);
    } else if let Number { odd: false, value } = n {
        println!("Even number: {}", value);
    }
}

// это печатает:
// Odd number: 1
// Even number: 2

match как шаблон

Армы match также являются шаблонами, так же как if let:

fn print_number(n: Number) {
    match n {
        Number { odd: true, value } => println!("Odd number: {}", value),
        Number { odd: false, value } => println!("Even number: {}", value),
    }
}

// это печатает то же самое, что и раньше

Исчерпывающие match

match должен быть исчерпывающим: хотя бы один арм должен соответствовать.

fn print_number(n: Number) {
    match n {
        Number { value: 1, .. } => println!("One"),
        Number { value: 2, .. } => println!("Two"),
        Number { value, .. } => println!("{}", value),
        // если бы последний арм не существовал, мы бы получили ошибку во время компиляции
    }
}

Если это сложно, можно использовать _ как "шаблон на все случаи":

fn print_number(n: Number) {
    match n.value {
        1 => println!("One"),
        2 => println!("Two"),
        _ => println!("{}", n.value),
    }
}

Методы

Вы можете объявлять методы для своих типов:

struct Number {
    odd: bool,
    value: i32,
}

impl Number {
    fn is_strictly_positive(self) -> bool {
        self.value > 0
    }
}

И использовать их, как обычно:

fn main() {
    let minus_two = Number {
        odd: false,
        value: -2,
    };
    println!("positive? {}", minus_two.is_strictly_positive());
    // это печатает "positive? false"
}

Неизменяемость

Привязки переменных по умолчанию неизменяемы, что означает, что их внутреннее состояние не может быть изменено:

fn main() {
    let n = Number {
        odd: true,
        value: 17,
    };
    n.odd = false; // ошибка: нельзя присваивать `n.odd`, так как `n` не объявлено изменяемым
}

И также они не могут быть присвоены повторно:

fn main() {
    let n = Number {
        odd: true,
        value: 17,
    };
    n = Number {
        odd: false,
        value: 22,
    }; // ошибка: нельзя присваивать дважды неизменяемой переменной `n`
}

mut делает привязку переменной изменяемой:

fn main() {
    let mut n = Number {
        odd: true,
        value: 17,
    };
    n.value = 19; // все нормально
}

Трейты

Трейты - это то, что могут иметь несколько типов:

trait Signed {
    fn is_strictly_negative(self) -> bool;
}

Правила сиротства

Вы можете реализовать:

  • один из ваших трейтов для чужого типа
  • чужой трейт для одного из ваших типов

но не чужой трейт для чужого типа.

Эти правила называются "правилами сиротства".

Вот реализация нашего трейта для нашего типа:

impl Signed for Number {
    fn is_strictly_negative(self) -> bool {
        self.value < 0
    }
}

fn main() {
    let n = Number { odd: false, value: -44 };
    println!("{}", n.is_strictly_negative()); // печатает "true"
}

Наш трейт для чужого типа (даже примитивного):

impl Signed for i32 {
    fn is_strictly_negative(self) -> bool {
        self < 0
    }
}

fn main() {
    let n: i32 = -44;
    println!("{}", n.is_strictly_negative()); // печатает "true"
}

Чужой трейт для нашего типа:

// трейт `Neg` используется для перегрузки унарного оператора минус.
impl std::ops::Neg for Number {
    type Output = Number;

    fn neg(self) -> Number {
        Number {
            value: -self.value,
            odd: self.odd,
        }        
    }
}

fn main() {
    let n = Number { odd: true, value: 987 };
    let m = -n; // это возможно только потому, что мы реализовали `Neg`
    println!("{}", m.value); // печатает "-987"
}

Тип Self

Блок impl всегда для типа, поэтому внутри этого блока Self означает этот тип:

impl std::ops::Neg for Number {
    type Output = Self;

    fn neg(self) -> Self {
        Self {
            value: -self.value,
            odd: self.odd,
        }        
    }
}

Маркерные трейты

Некоторые трейты являются маркерными - они не указывают, что тип реализует какие-то методы, они указывают, что с типом можно делать определенные вещи.

Нап

ример, i32 реализует трейт Copy (коротко, i32 является Copy), так что это работает:

fn main() {
    let a: i32 = 15;
    let b = a; // `a` копируется
    let c = a; // `a` снова копируется
}

И это тоже работает:

fn print_i32(x: i32) {
    println!("x = {}", x);
}

fn main() {
    let a: i32 = 15;
    print_i32(a); // `a` копируется
    print_i32(a); // `a` снова копируется
}

Но структура Number не является Copy, поэтому это не работает:

fn main() {
    let n = Number { odd: true, value: 51 };
    let m = n; // `n` перемещается в `m`
    let o = n; // ошибка: использование перемещенного значения: `n`
}

И это тоже не работает:

fn print_number(n: Number) {
    println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}

fn main() {
    let n = Number { odd: true, value: 51 };
    print_number(n); // `n` перемещается
    print_number(n); // ошибка: использование перемещенного значения: `n`
}

Но это работает, если print_number принимает неизменяемую ссылку:

fn print_number(n: &Number) {
    println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}

fn main() {
    let n = Number { odd: true, value: 51 };
    print_number(&n); // `n` заимствовано на время вызова
    print_number(&n); // `n` заимствовано снова
}

Это также работает, если функция принимает изменяемую ссылку - но только если привязка переменной также mut.

fn invert(n: &mut Number) {
    n.value = -n.value;
}

fn print_number(n: &Number) {
    println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}

fn main() {
    // на этот раз `n` изменяемая
    let mut n = Number { odd: true, value: 51 };
    print_number(&n);
    invert(&mut n); // `n` заимствовано изменяемо - все явно
    print_number(&n);
}

Получатели методов трейтов

Методы трейтов также могут принимать self по ссылке или изменяемой ссылке:

impl std::clone::Clone for Number {
    fn clone(&self) -> Self {
        Self { ..*self }
    }
}

При вызове методов трейтов получатель заимствуется неявно:

fn main() {
    let n = Number { odd: true, value: 51 };
    let mut m = n.clone();
    m.value += 100;
    
    print_number(&n);
    print_number(&m);
}

Чтобы подчеркнуть это: эти выражения эквивалентны:

let m = n.clone();

let m = std::clone::Clone::clone(&n);

Маркерные трейты, такие как Copy, не имеют методов:

// заметьте: `Copy` требует, чтобы был реализован также `Clone`
impl std::clone::Clone for Number {
    fn clone(&self) -> Self {
        Self { ..*self }
    }
}

impl std::marker::Copy for Number {}

Теперь Clone все еще можно использовать:

fn main() {
    let n = Number { odd: true, value: 51 };
    let m = n.clone();
    let o = n.clone();
}

Но значения Number больше не будут перемещаться:

fn main() {
    let n = Number { odd: true, value: 51 };
    let m = n; // `m` является копией `n`
    let o = n; // тоже самое. `n` не перемещается и не заимствуется.
}

Автоматическое создание трейтов

Некоторые трейты настолько распространены, что они могут быть автоматически реализованы с использованием атрибута derive:

#[derive(Clone, Copy)]
struct Number {
    odd: bool,
    value: i32,
}

// это разворачивается в `impl Clone for Number` и `impl Copy for Number`.

Дженерики

Дженерик-функции

Функции могут быть обобщенными:

fn foobar<T>(arg: T) {
    // делаем что-то с `arg`
}

Они могут иметь несколько параметров типа, которые затем могут использоваться в объявлении функции и в ее теле, вместо конкретных типов:

fn foobar<L, R>(left: L, right: R) {
    // делаем что-то с `left` и `right`
}

Ограничения параметров типа (границы трейтов)

Параметры типа обычно имеют ограничения, чтобы вы могли что-то с ними делать.

Простые ограничения - это просто имена трейтов:

fn print<T: Display>(value: T) {
    println!("value = {}", value);
}

fn print<T: Debug>(value: T) {
    println!("value = {:?}", value);
}

Существует более длинный синтаксис для ограничений параметров типа:

fn print<T>(value: T)
where
    T: Display,
{
    println!("value = {}", value);
}

Ограничения могут быть более сложными: они могут требовать, чтобы параметр типа реализовывал несколько трейтов:

use std::fmt::Debug;

fn compare<T>(left: T, right: T)
where
    T: Debug + PartialEq,
{
    println!("{:?} {} {:?}", left, if left == right { "==" } else { "!=" }, right);
}

fn main() {
    compare("tea", "coffee");
    // печатает: "tea" != "coffee"
}

Мономорфизация

Обобщенные функции можно рассматривать как пространства имен, содержащие бесконечное количество функций с разными конкретными типами.

Так же, как с crate, модулями и типами, обобщенные функции можно "исследовать" (навигация) с помощью ::.

fn main() {
    use std::any::type_name;
    println!("{}", type_name::<i32>()); // печатает "i32"

    println!("{}", type_name::<(f64, char)>()); // печатает "(f64, char)"
}

Это любовно называется синтаксисом turbofish, потому что ::<> выглядит как рыбка.

Дженерик-структуры

Структуры также могут быть обобщенными:

struct Pair<T> {
    a: T,
    b: T,
}

fn print_type_name<T>(_val: &T) {
    println!("{}", std::any::type_name::<T>());
}

fn main() {
    let p1 = Pair { a: 3, b: 9 };
    let p2 = Pair { a: true, b: false };
    print_type_name(&p1); // печатает "Pair<i32>"
    print_type_name(&p2); // печатает "Pair<bool>"
}

Пример: Vec

Тип стандартной библиотеки Vec (~ массив в куче), является обобщенным:

fn main() {
    let mut v1 = Vec::new();
    v1.push(1);
    let mut v2 = Vec::new();
    v2.push(false);
    print_type_name(&v1); // печатает "Vec<i32>"
    print_type_name(&v2); // печатает "Vec<bool>"
}

Говоря о Vec, он имеет макрос, который более или менее предоставляет "литералы вектора":

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![true, false, true];
    print_type_name(&v1); // печатает "Vec<i32>"
    print_type_name(&v2); // печатает "Vec<bool>"
}

Макросы

Все name!(), name![] или name!{} вызывают макрос. Макросы просто разворачиваются в обычный код.

Фактически, println - это макрос:

fn main() {
    println!("{}", "Hello there!");
}

Это разворачивается в нечто, что имеет такой же эффект, как:

fn main() {
    use std::io::{self, Write};
    io::stdout().lock().write_all(b"Hello there!\n").unwrap();
}

Макрос panic!

panic также является макросом. Он жестоко останавливает выполнение с сообщением об ошибке и именем файла / номером строки ошибки, если включен:

fn main() {
    panic!("This panics");
}
// вывод: thread 'main' panicked at '

This panics', src/main.rs:3:5

Функции, вызывающие панику

Некоторые методы также вызывают панику. Например, тип Option может содержать что-то или ничего. Если вызвать .unwrap() на нем, и он содержит ничего, это вызывает панику:

fn main() {
    let o1: Option<i32> = Some(128);
    o1.unwrap(); // это нормально

    let o2: Option<i32> = None;
    o2.unwrap(); // это вызывает панику!
}
// вывод: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value

Перечисления (суммарные типы)

Option - это не структура, а перечисление с двумя вариантами.

enum Option<T> {
    None,
    Some(T),
}

impl<T> Option<T> {
    fn unwrap(self) -> T {
        // варианты перечислений можно использовать в шаблонах:
        match self {
            Self::Some(t) => t,
            Self::None => panic!(".unwrap() called on a None option"),
        }
    }
}

use self::Option::{None, Some};

fn main() {
    let o1: Option<i32> = Some(128);
    o1.unwrap(); // это нормально

    let o2: Option<i32> = None;
    o2.unwrap(); // это вызывает панику!
// вывод: thread 'main' panicked at '.unwrap() called on a None option', src/mai

Result также является перечислением, которое может содержать что-то или ошибку:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Он также вызывает панику, когда разворачивается и содержит ошибку.

Время жизни

Привязки переменных имеют "время жизни":

fn main() {
    // `x` еще не существует
    {
        let x = 42; // `x` начинает существовать
        println!("x = {}", x);
        // `x` перестает существовать
    }
    // `x` больше не существует
}

Аналогично, ссылки имеют время жизни:

fn main() {
    // `x` еще не существует
    {
        let x = 42; // `x` начинает существовать
        let x_ref = &x; // `x_ref` начинает существовать - оно заимствует `x`
        println!("x_ref = {}", x_ref);
        // `x_ref` перестает существовать
        // `x` перестает существовать
    }
    // `x` больше не существует
}

Время жизни ссылки не может превышать время жизни привязки переменной, которую она заимствует:

fn main() {
    let x_ref = {
        let x = 42;
        &x
    };
    println!("x_ref = {}", x_ref);
    // ошибка: `x` живет недостаточно долго
}

Правила заимствования (одно или несколько неизменяемых заимствований XOR одно изменяемое заимствование)

Привязка переменной может быть неизменяемо заимствована несколько раз:

fn main() {
    let x = 42;
    let x_ref1 = &x;
    let x_ref2 = &x;
    let x_ref3 = &x;
    println!("{} {} {}", x_ref1, x_ref2, x_ref3);
}

Во время заимствования переменную нельзя изменять:

fn main() {
    let mut x = 42;
    let x_ref = &x;
    x = 13;
    println!("x_ref = {}", x_ref);
    // ошибка: нельзя присваивать `x`, потому что она заимствована
}

Во время неизменяемого заимствования переменную нельзя заимствовать изменяемо:

fn main() {
    let mut x = 42;
    let x_ref1 = &x;
    let x_ref2 = &mut x;
    // ошибка: нельзя заимствовать `x` как изменяемую, потому что она уже заимствована как неизменяемая
    println!("x_ref1 = {}", x_ref1);
}

Функции с обобщенными параметрами времени жизни

Ссылки в аргументах функции также имеют время жизни:

fn print(x: &i32) {
    // `x` заимствовано (снаружи) на все время вызова этой функции.
}

Функции с аргументами-ссылками можно вызывать с заимствованиями, которые имеют разное время жизни, поэтому:

Все функции, принимающие ссылки, являются обобщенными.

Время жизни - это параметры обобщений.

Имена времени жизни начинаются с одиночной кавычки ':

// опущенное (неназванное) время жизни:
fn print(x: &i32) {}

// названное время жизни:
fn print<'a>(x: &'a i32) {}

Это позволяет возвращать ссылки, время жизни которых зависит от времени жизни аргументов:

struct Number {
    value: i32,
}

fn number_value<'a>(num: &'a Number) -> &'a i32 {
    &num.value
}

fn main() {
    let n = Number { value: 47 };
    let v = number_value(&n);
    // `v` заимствует `n` (неизменяемо), таким образом: `v` не может пережить `n`.
    // Пока существует `v`, `n` не может быть заимствован изменяемо, изменен, перемещен и т.д.
}

Опущение времени жизни

Когда существует одно входное время жизни, его не нужно называть, и все имеет одно и то же время жизни, поэтому две функции ниже эквивалентны:

fn number_value<'a>(num: &'a Number) -> &'a i32 {
    &num.value
}

fn number_value(num: &Number) -> &i32 {
    &num.value
}

Структуры с обобщенным временем жизни

Структуры также могут быть обобщенными по времени жизни, что позволяет им содержать ссылки:

struct NumRef<'a> {
    x: &'a i32,
}

fn main() {
    let x: i32 = 99;
    let x_ref = NumRef { x: &x };
    // `x_ref` не может пережить `x` и т.д.
}

Тот же код, но с дополнительной функцией:

struct NumRef<'a> {
    x: &'a i32,
}

fn as_num_ref<'a>(x: &'a i32) -> NumRef<'a> {
    NumRef { x: &x }
}

fn main() {
    let x: i32 = 99;
    let x_ref = as_num_ref(&x);
    // `x_ref` не может пережить `x` и т.д.
}

Тот же код, но с "опущенным" временем жизни:

struct NumRef<'a> {
    x: &'a i32,
}

fn as_num_ref(x: &i32) -> NumRef<'_> {
    NumRef { x: &x }
}

fn main() {
    let x: i32 = 99;
    let x_ref = as_num_ref(&x);
    // `x_ref` не может пережить `x` и т.д.
}

Реализации с обобщенным временем жизни

Блоки impl могут быть обобщенными по времени жизни:

impl<'a> NumRef<'a> {
    fn as_i32_ref(&'a self) -> &'a i32 {
        self.x
    }
}

fn main() {
    let x: i32 = 99;
    let x_num_ref = NumRef { x: &x };
    let x_i32_ref = x_num_ref.as_i32_ref();
    // ни одна из ссылок не может пережить `x`
}

Но вы также можете использовать опущение:

impl<'a> NumRef<'a> {
    fn as_i32_ref(&self) -> &i32 {
        self.x
    }
}

И можно опустить еще сильнее, если вам никогда не нужно имя:

impl NumRef<'_> {
    fn as_i32_ref(&self) -> &i32 {
        self.x
    }
}

Время жизни 'static

Существует особое время жизни, названное 'static, которое действительно на протяжении всей жизни программы.

Строковые литералы являются 'static:

struct Person {
    name: &'static str,
}

fn main() {
    let p = Person {
        name: "fasterthanlime",
    };
}

Но ссылки на String не являются 'static:

struct Person {
    name: &'static str,
}

fn main() {
    let name = format!("fasterthan{}", "lime");
    let p = Person { name: &name };
    // ошибка: `name` живет недостаточно долго
}

В последнем примере локальная переменная name не является &'static str, это String. Она была выделена динамически и будет освобождена. Ее время жизни меньше, чем время жизни всей программы (даже если она находится в main).

Чтобы хранить нестатическую строку в Person, она должна либо:

A) быть обобщенной по времени жизни:

struct Person<'a> {
    name: &'a str,
}

fn main() {
    let name = format!("fasterthan{}", "lime");
    let p = Person { name: &name };
    // `p` не может пережить `name`
}

или

B) взять на себя владение строкой:

struct Person {
    name: String,
}

fn main() {
    let name = format!("fasterthan{}", "lime");
    let p = Person { name: name };
    // `name` был перемещен в `p`, их время жизни больше не связано.
}

Сокращение при присваивании литералов структур

Говоря об этом: в литерале структуры, когда поле устанавливается на переменную с тем же именем:

let p = Person { name: name };

Можно сократить это так:

let p = Person { name };

Инструменты, такие как clippy, предложат внести эти изменения и даже применят исправление программно, если вы это разрешите.

Собственные и ссылочные типы

Для многих типов в Rust существуют собственные и несобственные варианты:

  • Строки: String является собственным, &str является ссылкой.
  • Пути: PathBuf является собственным, &Path является ссылкой.
  • Коллекции: Vec<T> является собственным, &[T] является ссылкой.

Срезы

Rust имеет срезы - это ссылки на несколько смежных элементов. Вы можете заимствовать срез вектора, например:

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    let v2 = &v[2..4];
    println!("v2 = {:?}", v2);
}
// вывод:
// v2 = [3, 4]

Перегрузка операторов

Вышеописанное не является магией. Оператор индексирования (foo[index]) перегружен с использованием трейтов Index и IndexMut.

Синтаксис .. - это просто литералы диапазона. Диапазоны - это просто несколько структур, определенных в стандартной библиотеке.

Они могут быть открытыми и их правый предел может быть включен, если перед ним стоит =.

fn main() {
    // 0 или больше
    println!("{:?}", (0..).contains(&100)); // true
    // строго меньше 20
    println!("{:?}", (..20).contains(&20)); // false
    // 20 или меньше
    println!("{:?}", (..=20).contains(&20)); // true
    // только 3, 4, 5
    println!("{:?}", (3..6).contains(&4)); // true
}

Правила заимствования и срезы

Правила заимствования применимы к срезам.

fn tail(s: &[u8]) -> &[u8] {
    &s[1..]
}

fn main() {
    let x = &[1, 2, 3, 4, 5];
    let y = tail(x);
    println!("y = {:?}", y);
}

Это то же самое, что и:

fn tail<'a>(s: &'a [u8]) -> &'a [u8] {
    &s[1..]
}

Это легально:

fn main() {
    let y = {
        let x = &[1, 2, 3, 4, 5];
        tail(x)
    };
    println!("y = {:?}", y);
}
// но только потому, что [1, 2, 3, 4, 5] является 'static массивом.

Так что это незаконно:

fn main() {
    let y = {
        let v = vec![1, 2, 3, 4, 5];
        tail(&v)
        // ошибка: `v` живет недостаточно долго
    };
    println!("y = {:?}", y);
}
// потому что вектор выделяется в куче и имеет не 'static время жизни.

Срезы строк (&str)

Значения &str на самом деле являются срезами.

fn file_ext(name: &str) -> Option<&str> {
    // это не создает новую строку - возвращает
    // срез аргумента.
    name.split(".").last()
}

fn main() {
    let name = "Read me. Or don't.txt";
    if let Some(ext) = file_ext(name) {
        println!("file extension: {}",

 ext);
    } else {
        println!("no file extension");
    }
}

...так что правила заимствования применимы и здесь:

fn main() {
    let ext = {
        let name = String::from("Read me. Or don't.txt");
        file_ext(&name).unwrap_or("")
        // ошибка: `name` живет недостаточно долго
    };
    println!("extension: {:?}", ext);
}

Ошибочные функции (Result)

Функции, которые могут завершиться неудачей, обычно возвращают Result:

fn main() {
    let s = std::str::from_utf8(&[240, 159, 141, 137]);
    println!("{:?}", s);
    // печатает: Ok("🍉")

    let s = std::str::from_utf8(&[195, 40]);
    println!("{:?}", s);
    // печатает: Err(Utf8Error { valid_up_to: 0, error_len: Some(1) })
}

Если вы хотите вызвать панику в случае неудачи, вы можете использовать .unwrap():

fn main() {
    let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
    println!("{:?}", s);
    // печатает: "🍉"

    let s = std::str::from_utf8(&[195, 40]).unwrap();
    // вывод: thread 'main' panicked at 'called `Result::unwrap()`
    // on an `Err` value: Utf8Error { valid_up_to: 0, error_len: Some(1) }',
    // src/libcore/result.rs:1165:5
}

Или .expect() для пользовательского сообщения:

fn main() {
    let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8");
    // вывод: thread 'main' panicked at 'valid utf-8: Utf8Error
    // { valid_up_to: 0, error_len: Some(1) }', src/libcore/result.rs:1165:5
}

Или, вы можете использовать match:

fn main() {
    match std::str::from_utf8(&[240, 159, 141, 137]) {
        Ok(s) => println!("{}", s),
        Err(e) => panic!(e),
    }
    // печатает 🍉
}

Или можно использовать if let:

fn main() {
    if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) {
        println!("{}", s);
    }
    // печатает 🍉
}

Или можно передать ошибку вверх по цепочке:

fn main() -> Result<(), std::str::Utf8Error> {
    match std::str::from_utf8(&[240, 159, 141, 137]) {
        Ok(s) => println!("{}", s),
        Err(e) => return Err(e),
    }
    Ok(())
}

Или можно использовать ? для краткости:

fn main() -> Result<(), std::str::Utf8Error> {
    let s = std::str::from_utf8(&[240, 159, 141, 137])?;
    println!("{}", s);
    Ok(())
}

Разыменование

Оператор * может использоваться для разыменования, но вам не нужно это делать для доступа к полям или вызова методов:

struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p = Point { x: 1.0, y: 3.0 };
    let p_ref = &p;
    println!("({}, {})", p_ref.x, p_ref.y);
    // печатает `(1, 3)`
}

И вы можете делать это, только если тип Copy:

struct Point {
    x: f64,
    y: f64,
}

fn negate(p: Point) -> Point {
    Point {
        x: -p.x,
        y: -p.y,
    }
}

fn main() {
    let p = Point { x: 1.0, y: 3.0 };
    let p_ref = &p;
    negate(*p_ref);
    // ошибка: нельзя перемещать из `*p_ref`, который находится за общей ссылкой
}

Теперь Point является Copy:

#[derive(Clone, Copy)]
struct Point {
    x: f64,
    y: f64,
}

fn negate(p: Point) -> Point {
    Point {
        x: -p.x,
        y: -p.y,
    }
}

fn main() {
    let p = Point { x: 1.0, y: 3.0 };
    let p_ref = &p;
    negate(*p_ref); // ...и теперь это работает
}

Типы функций, замыкания

Замыкания - это просто функции типа Fn, FnMut или FnOnce с захваченным контекстом.

Их параметры - это список имен через запятую внутри пары вертикальных черт (|). Им не нужны фигурные скобки, если вы не хотите иметь несколько операторов.

fn for_each_planet<F>(f: F)
    where F: Fn(&'static str)
{
    f("Earth");
    f("Mars");
    f("Jupiter");
}

fn main() {
    for_each_planet(|planet| println!("Hello, {}", planet));
}

// печатает:
// Hello, Earth
// Hello, Mars
// Hello, Jupiter

Правила заимствования применимы и к ним:

fn for_each_planet<F>(f: F)
    where F: Fn(&'static str)
{
    f("Earth");
    f("Mars");
    f("Jupiter");
}

fn main() {
    let greeting = String::from("Good to see you");
    for_each_planet(|planet| println!("{}, {}", greeting, planet));
    // наше замыкание заимствует `greeting`, поэтому оно не может пережить его
}

Например, это не сработает:

fn for_each_planet<F>(f: F)
    where F: Fn(&'static str) + 'static // `F` теперь должно иметь время жизни `'static`
{
    f("Earth");
    f("Mars");
    f("Jupiter");
}

fn main() {
    let greeting = String::from("Good to see you");
    for_each_planet(|planet| println!("{}, {}", greeting, planet));
    // ошибка: замыкание может пережить текущую функцию, но оно заимствует
    // `greeting`, которое принадлежит текущей функции
}

Но это сработает:

fn main() {
    let greeting = String::from("You're doing great");
    for_each_planet(move |planet| println!("{}, {}", greeting, planet));
    // `greeting` больше не заимствовано, оно *перемещено* в
    // замыкание.
}

FnMut и правила заимствования

FnMut нужно изменяемо заимствовать, чтобы его вызвать, так что его можно вызывать только один раз за раз.

Это законно:

fn foobar<F>(f: F)
    where F: Fn(i32) -> i32
{
    println!("{}", f(f(2)));
}

fn main() {
    foobar(|x| x * 2);
}
// вывод: 8

А это нет:

fn foobar<F>(mut f: F)
    where F: FnMut(i32) -> i32
{
    println!("{}", f(f(2)));
    // ошибка: нельзя заимствовать `f` как изменяемую более одного раза за раз
}

fn main() {
    foobar(|x| x * 2);
}

Это снова законно:

fn foobar<F>(mut f: F)
    where F: FnMut(i32) -> i32
{
    let tmp = f(2);
    println!("{}", f(tmp));
}

fn main() {
    foobar(|x| x * 2);
}
// вывод: 8

FnMut существует, потому что некоторые замыкания изменяемо заимствуют локальные переменные:

fn foobar<F>(mut f: F)
    where F: FnMut(i32) -> i32
{
    let tmp = f(2);
    println!("{}", f(tmp));
}

fn main() {
    let mut acc = 2;
    foobar(|x| {
        acc += 1;
        x * acc
    });
}
// вывод: 24

Эти замыкания нельзя передать функциям, ожидающим Fn:

fn foobar<F>(f: F)
    where F: Fn(i32) -> i32
{
    println!("{}", f(f(2)));
}

fn main() {
    let mut acc = 2;
    foobar(|x| {
        acc += 1;
        // ошибка: нельзя присваивать `acc`, так как это
        // захваченная переменная в замыкании `Fn`.
        // компилятор предлагает "изменить foobar
       

 // чтобы он принимал замыкания, которые реализуют `FnMut`"
        x * acc
    });
}

FnOnce замыкания

FnOnce замыкания можно вызвать только один раз. Они существуют, потому что некоторые замыкания перемещают переменные, которые были перемещены при захвате:

fn foobar<F>(f: F)
    where F: FnOnce() -> String
{
    println!("{}", f());
}

fn main() {
    let s = String::from("alright");
    foobar(move || s);
    // `s` был перемещен в наше замыкание, и наше
    // замыкание перемещает его к вызывающему, возвращая
    // его. Помните, что `String` не является `Copy`.
}

Это естественно соблюдается, так как FnOnce замыкания нужно переместить, чтобы их вызвать.

Так что, например, это незаконно:

fn foobar<F>(f: F)
    where F: FnOnce() -> String
{
    println!("{}", f());
    println!("{}", f());
    // ошибка: использование перемещенного значения: `f`
}

И если вам нужно доказательство, что наше замыкание действительно перемещает s, это также незаконно:

fn main() {
    let s = String::from("alright");
    foobar(move || s);
    foobar(move || s);
    // использование перемещенного значения: `s`
}

Но это законно:

fn main() {
    let s = String::from("alright");
    foobar(|| s.clone());
    foobar(|| s.clone());
}

Вот замыкание с двумя аргументами:

fn foobar<F>(x: i32, y: i32, is_greater: F)
    where F: Fn(i32, i32) -> bool
{
    let (greater, smaller) = if is_greater(x, y) {
        (x, y)
    } else {
        (y, x)
    };
    println!("{} is greater than {}", greater, smaller);
}

fn main() {
    foobar(32, 64, |x, y| x > y);
}

Вот замыкание, игнорирующее оба своих аргумента:

fn main() {
    foobar(32, 64, |_, _| panic!("Comparing is futile!"));
}

Вот немного тревожное замыкание:

fn countdown<F>(count: usize, tick: F)
    where F: Fn(usize)
{
    for i in (1..=count).rev() {
        tick(i);
    }
}

fn main() {
    countdown(3, |i| println!("tick {}...", i));
}
// вывод:
// tick 3...
// tick 2...
// tick 1...

Замыкание туалетного типа

И вот замыкание туалетного типа:

fn main() {
    countdown(3, |_| ());
}

Оно так называется, потому что |_| () выглядит как унитаз.

Циклы и итераторы

Все, что можно итерировать, можно использовать в цикле for in.

Мы только что видели использование диапазона, но это также работает с Vec:

fn main() {
    for i in vec![52, 49, 21] {
        println!("I like the number {}", i);
    }
}

И срез:

fn main() {
    for i in &[52, 49, 21] {
        println!("I like the number {}", i);
    }
}
// вывод:
// I like the number 52
// I like the number 49
// I like the number 21

И даже настоящий итератор:

fn main() {
    // примечание: `&str` также имеет итератор `.bytes()`.
    // Тип `char` в Rust - это "скалярное значение Unicode"
    for c in "rust".chars() {
        println!("Give me a {}", c);
    }
}
// вывод:
// Give me a r
// Give me a u
// Give me a s
// Give me a t

Даже если элементы итератора отфильтрованы и преобразованы:

fn main() {
    for c in "SuRPRISE INbOUND"
        .chars()
        .filter(|c| c.is_lowercase())
        .flat_map(|c| c.to_uppercase())
    {
        print!("{}", c);
    }
    println!();
}
// вывод: UB

Возвращение замыканий

Вы можете вернуть замыкание из функции:

fn make_tester(answer: String) -> impl Fn(&str) -> bool {
    move |challenge| {
        challenge == answer
    }
}

fn main() {
    // можно использовать `.into()` для выполнения преобразований
    // между различными типами, здесь `&'static str` и `String`
    let test = make_tester("hunter2".into());
    println!("{}", test("******"));
    println!("{}", test("hunter2"));
}

Захват в замыкания

Вы даже можете переместить ссылку на некоторые аргументы функции в замыкание, которое она возвращает:

fn make_tester<'a>(answer: &'a str) -> impl Fn(&str) -> bool + 'a {
    move |challenge| {
        challenge == answer
    }
}

fn main() {
    let test = make_tester("hunter2");
    println!("{}", test("*******"));
    println!("{}", test("hunter2"));
}
// вывод:
// false
// true

Или с опущенными временами жизни:

fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ {
    move |challenge| {
        challenge == answer
    }
}

Заключение

И на этом мы достигли отметки времени чтения в 30 минут, и вы должны быть в состоянии читать большинство кода Rust, который найдете в Интернете.

Писать на Rust - это совершенно другой опыт по сравнению с чтением Rust. С одной стороны, вы не читаете решение проблемы, вы действительно ее решаете. С другой стороны, компилятор Rust очень помогает.

Компилятор Rust имеет высококачественные диагностические сообщения (включая предложения) для всех ошибок, рассмотренных в этой статье.

И когда подсказки отсутствуют, команда компилятора не боится их добавлять.

Для получения дополнительного