1. 1. Introduction
  2. 2. Введение
  3. 3. C чего начать
  4. 4. Изучение Rust
    1. 4.1. Угадайка
    2. 4.2. Обедающие философы
    3. 4.3. Вызов кода на Rust из других языков
  5. 5. Синтаксис и семантика
    1. 5.1. Связывание имён
    2. 5.2. Функции
    3. 5.3. Простые типы
    4. 5.4. Комментарии
    5. 5.5. Конструкция `if`
    6. 5.6. Циклы
    7. 5.7. Владение
    8. 5.8. Ссылки и заимствование
    9. 5.9. Время жизни
    10. 5.10. Изменяемость
    11. 5.11. Структуры
    12. 5.12. Перечисления
    13. 5.13. Конструкция `match`
    14. 5.14. Шаблоны сопоставления `match`
    15. 5.15. Синтаксис методов
    16. 5.16. Вектора
    17. 5.17. Строки
    18. 5.18. Обобщённое программирование
    19. 5.19. Типажи
    20. 5.20. Типаж `Drop`
    21. 5.21. Конструкция `if let`
    22. 5.22. Типажи-объекты
    23. 5.23. Замыкания
    24. 5.24. Универсальный синтаксис вызова функций
    25. 5.25. Контейнеры и модули
    26. 5.26. `const` и `static`
    27. 5.27. Атрибуты
    28. 5.28. Псевдонимы типов
    29. 5.29. Приведение типов
    30. 5.30. Ассоциированные типы
    31. 5.31. Безразмерные типы
    32. 5.32. Перегрузка операций
    33. 5.33. Преобразования при разыменовании
    34. 5.34. Макросы
    35. 5.35. Сырые указатели
    36. 5.36. Небезопасный код
  6. 6. Эффективное использование Rust
    1. 6.1. Стек и куча
    2. 6.2. Тестирование
    3. 6.3. Условная компиляция
    4. 6.4. Документация
    5. 6.5. Итераторы
    6. 6.6. Многозадачность
    7. 6.7. Обработка ошибок
    8. 6.8. Выбор гарантий
    9. 6.9. Интерфейс внешних функций
    10. 6.10. Типажи `Borrow` и `AsRef`
    11. 6.11. Каналы сборок
    12. 6.12. Using Rust without the standard library
  7. 7. Нестабильные возможности Rust
    1. 7.1. Плагины к компилятору
    2. 7.2. Встроенный ассемблерный код
    3. 7.3. Без stdlib
    4. 7.4. Внутренние средства
    5. 7.5. Элементы языка
    6. 7.6. Продвинутое руководство по компоновке
    7. 7.7. Тесты производительности
    8. 7.8. Синтаксис упаковки и шаблоны `match`
    9. 7.9. Шаблоны `match` для срезов
    10. 7.10. Ассоциированные константы
    11. 7.11. Пользовательские менеджеры памяти
  8. 8. Глоссарий
  9. 9. Syntax Index
  10. 10. Библиография

Время жизни

Эта глава является одной из трёх, описывающих систему владения ресурсами Rust. Эта система представляет собой наиболее уникальную и привлекательную особенность Rust, о которой разработчики должны иметь полное представление. Владение — это то, как Rust достигает своей главной цели — безопасности памяти. Система владения включает в себя несколько различных концепций, каждая из которых рассматривается в своей собственной главе:

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

Мета

Прежде чем перейти к подробностям, отметим два важных момента в системе владения.

Rust сфокусирован на безопасности и скорости. Это достигается за счёт «абстракций с нулевой стоимостью» (zero-cost abstractions). Это значит, что в Rust стоимость абстракций должна быть настолько малой, насколько это возможно без ущерба для работоспособности. Система владения ресурсами — это яркий пример абстракции с нулевой стоимостью. Весь анализ, о котором мы будем говорить в этом руководстве, выполняется во время компиляции. Во время исполнения вы не платите за какую-либо из возможностей ничего.

Тем не менее, эта система всё же имеет определённую стоимость: кривая обучения. Многие пользователи Rust занимаются тем, что мы зовём «борьбой с проверкой заимствования» — компилятор Rust отказывается компилировать программу, которая по мнению автора является абсолютно правильной. Это часто происходит потому, что мысленное представление программиста о том, как должно работать владение, не совпадает с реальными правилами, которыми оперирует Rust. Вы, наверное, поначалу также будете испытывать подобные трудности. Однако существует и хорошая новость: более опытные разработчики на Rust говорят, что чем больше они работают с правилами системы владения, тем меньше они борются с компилятором.

Имея это в виду, давайте перейдём к изучению системы владения.

Время жизни

Одалживание ссылки на ресурс, которым кто-то владеет, может быть довольно сложным. Например, представьте себе следующую последовательность операций:

Ой-ой! Ваша ссылка указывает на недопустимый ресурс. Это называется «висячий указатель» или «использование после освобождения», когда ресурсом является память.

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

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

// неявно
fn foo(x: &i32) {
}

// явно
fn bar<'a>(x: &'a i32) {
}Run

Читается 'a как «время жизни a». Технически, все ссылки имеют некоторое время жизни, связанное с ними, но компилятор позволяет опускать его в общих случаях. Прежде чем мы перейдем к этому, давайте разберем пример ниже, с явным указанием времени жизни:

fn bar<'a>(...)Run

Эта часть объявляет параметры времени жизни. Она говорит, что bar имеет один параметр времени жизни, 'a. Если бы в качестве параметров функции у нас было две ссылки, то это выглядело бы так:

fn bar<'a, 'b>(...)Run

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

...(x: &'a i32)Run

Если бы мы хотели &mut ссылку, то сделали бы так:

...(x: &'a mut i32)Run

Если вы сравните &mut i32 с &'a mut i32, то увидите, что они отличаются только определением времени жизни 'a, написанным между & и mut i32. &mut i32 читается как «изменяемая ссылка на i32», а &'a mut i32 — как «изменяемая ссылка на i32 со временем жизни 'a».

Внутри struct'ов

Вы также должны будете явно указать время жизни при работе со struct'ми:

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

fn main() {
    let y = &5; // то же самое, что и `let _y = 5; let y = &_y;`
    let f = Foo { x: y };

    println!("{}", f.x);
}Run

Как вы можете заметить, структуры также могут иметь время жизни. Так же как и функции,

struct Foo<'a> {Run

объявляет время жизни и

x: &'a i32,Run

использует его. Почему же мы должны определять время жизни здесь? Мы должны убедиться, что ссылка на Foo не может жить дольше, чем ссылка на i32, содержащаяся в нем.

Блоки impl

Давайте реализуем метод для Foo:

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

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

fn main() {
    let y = &5; // то же самое, что и `let _y = 5; let y = &_y;`
    let f = Foo { x: y };

    println!("x is: {}", f.x());
}Run

Как вы можете видеть, нам нужно объявить время жизни для Foo в строке с impl. Мы повторяем 'a дважды, как в функциях: impl<'a> определяет время жизни 'a, и Foo<'a> использует его.

Несколько времён жизни (Multiple lifetimes)

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

fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str {Run

Этот код говорит, что x и y находятся в одной области видимости друг с другом, и что возвращаемое значение живо на протяжении той же области видимости. Если вы хотите, чтобы x и y имели разные времена жизни, вы должны использовать параметры нескольких времён жизни:

fn x_or_y<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {Run

В этом примере x и y имеют различные области видимости, но возвращаемое значение имеет то же время жизни, что и x.

Осмысливаем области видимости (Thinking in scopes)

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

fn main() {
    let y = &5;     // -+ y входит в область видимости
                    //  |
    // что-то       //  |
                    //  |
}                   // -+ y выходит из области видимостиRun

Добавим нашу структуру Foo:

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

fn main() {
    let y = &5;           // -+ y входит в область видимости
    let f = Foo { x: y }; // -+ f входит в область видимости
    // что-то             //  |
                          //  |
}                         // -+ f и y выходят из области видимостиRun

Наша f живет в области видимости y, поэтому все работает. Что же произойдёт, если это будет не так? Этот код не будет работать:

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

fn main() {
    let x;                    // -+ x входит в область видимости
                              //  |
    {                         //  |
        let y = &5;           // ---+ y входит в область видимости
        let f = Foo { x: y }; // ---+ f входит в область видимости
        x = &f.x;             //  | | здесь ошибка
    }                         // ---+ f и y выходят из области видимости
                              //  |
    println!("{}", x);        //  |
}                             // -+ x выходит из области видимостиRun

Уф! Как вы можете видеть здесь, области видимости f и y меньше, чем область видимости x. Но когда мы выполняем x = &f.x, мы присваиваем x ссылку на что-то, что вот-вот выйдет из области видимости.

Присвоение имени времени жизни — это способ задать имя области видимости. Чтобы думать о чём-то, нужно иметь название для этого.

'static

Время жизни с именем «static» — особенное. Оно обозначает, что что-то имеет время жизни, равное времени жизни всей программы. Большинство программистов на Rust впервые сталкиваются с 'static, когда имеют дело со строками:

let x: &'static str = "Привет, мир.";Run

Строковые литералы имеют тип &'static str, потому что ссылка всегда действительна: строки располагаются в сегменте данных конечного двоичного файла. Другой пример — глобальные переменные:

static FOO: i32 = 5;
let x: &'static i32 = &FOO;Run

В этом примере i32 добавляется в сегмент данных двоичного файла, а x ссылается на него.

Опускание времени жизни

В Rust есть мощный локальный вывод типов. Однако, сигнатуры объявлений верхнего уровня не выводятся, чтобы можно было рассуждать о типах на основании одних лишь сигнатур. Из соображений удобства, введён ограниченный механизм вывода типов сигнатур функций, называемый «опускание времени жизни» («lifetime elision»). Он выводит типы на основании только элементов сигнатуры — тело функции при этом не учитывается. При этом его назначение — это вывести лишь параметры времени жизни аргументов. Для этого он реализует три простых правила. Таким образом, опускание времени жизни упрощает написание сигнатур, одновременно не скрывая реальные типы аргументов.

Когда речь идет о неявном времени жизни, мы используем термины входное время жизни (input lifetime) и выходное время жизни (output lifetime). Входное время жизни связано с передаваемыми в функцию параметрами, а выходное время жизни связано с возвращаемым функцией значением. Например, эта функция имеет входное время жизни:

fn foo<'a>(bar: &'a str)Run

А эта имеет выходное время жизни:

fn foo<'a>() -> &'a strRun

Эта же имеет как входное, так и выходное время жизни:

fn foo<'a>(bar: &'a str) -> &'a strRun

Ниже представлены три правила:

В противном случае, неявное задание выходного времени жизни является ошибкой.

Примеры

Вот некоторые примеры функций, представленные в двух видах: с явно и неявно заданным временем жизни:

fn print(s: &str); // неявно
fn print<'a>(s: &'a str); // явно

fn debug(lvl: u32, s: &str); // неявно
fn debug<'a>(lvl: u32, s: &'a str); // явно

// В предыдущем примере для `lvl` не требуется указывать время жизни, потому что
// это не ссылка (`&`). Только элементы, связанные с ссылками (например, такие
// как структура, содержащая ссылку) требуют указания времени жизни.

fn substr(s: &str, until: u32) -> &str; // неявно
fn substr<'a>(s: &'a str, until: u32) -> &'a str; // явно

fn get_str() -> &str; // НЕКОРРЕКТНО, нет входных параметров

fn frob(s: &str, t: &str) -> &str; // НЕКОРРЕКТНО, два входных параметра
fn frob<'a, 'b>(s: &'a str, t: &'b str) -> &str; // Развёрнуто: Выходное время жизни неясно

fn get_mut(&mut self) -> &mut T; // неявно
fn get_mut<'a>(&'a mut self) -> &'a mut T; // явно

fn args<T:ToCStr>(&mut self, args: &[T]) -> &mut Command // неявно
fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command // явно

fn new(buf: &mut [u8]) -> BufWriter; // неявно
fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a> // явноRun