Проверка ссылок с помощью Lifetimes

Когда мы говорили о ссылках в Главе 4, мы опустили весьма важную деталь: каждая ссылка в Rust имеет время жизни, определяющее область действия, в которой ссылка является действительной. В большинстве случаев времена жизни выводятся неявно, однако иногда времена жизни ссылок могут неоднозначно соотноситься между собой. В таких случаях нам необходимо явно указать времена жизни, используя синтаксис шаблонных параметров времен жизни чтобы компилятор убедился в том, что ссылка, которую мы используем, всегда действительна.

Да, это немного необычно и может отличаться от концепций, к которым вы привыкли в других языках программирования. Как бы то ни было, времена жизни — наиболее яркая отличительная черта языка Rust.

Эта тема настолько обширна, что в этой главе мы сможем изложить только синтаксис и общие концепции. Глава 19 содержит больше подробной информации обо всех возможностях времён жизни.

Lifetimes защищают программу от недействительных ссылок

Главная цель существования времён жизни — это предотвращение использования недействительных ссылок. Это весьма коварная ошибка, которую трудно заметить. Для примера, давайте рассмотрим код (10-18). Здесь демонстрируется поведение переменных в различных областях видимости. Во внешней области видимости мы декларируем переменную r без её инициализации. Во внутренней области видимости мы декларируем переменную x и инициализируем её значением 5. Далее в этой же области видимости мы пытаемся присвоить переменной r ссылку на переменную x. Затем, когда внутренняя область видимости закончилась, мы хотим напечатать содержимое переменной r во внешней области видимости:

fn main(){
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}

Пример 10-18: Попытка использования ссылки, значение которой вышло из области видимости

Неинициализированные переменные не могут быть использованы

Следующие несколько примеров демонстрируют объявление переменных без их инициализации таким образом, что имя переменной существует во внешней области видимости. Это может показаться странным, поскольку Rust не имеет null-значений. Однако, если мы попробуем использовать переменную без её инициализации — мы получим ошибку компиляции. Попробуйте сами!

Если мы попробуем скомпилировать этот код — мы получим следующую ошибку:

error: `x` does not live long enough
   |
6  |         r = &x;
   |              - borrow occurs here
7  |     }
   |     ^ `x` dropped here while still borrowed
...
10 | }
   | - borrowed value needs to live until here

Переменная x живёт «недостаточно долго». Но почему? Всё дело в том, что x выходит из области видимости когда мы закрываем фигурную скобку в строке 7. Но r всё ещё доступна во внешней области видимости, которая больше внутренней. Поэтому мы говорим, что r «живёт дольше». Если бы Rust позволил этому коду работать, переменная r указывала бы на память, которая была освобождена когда x вышла из области видимости. Любые действия с переменной r привели бы к некорректной работе программы. Но как же Rust понимает что этот код — неправильный?

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

В состав компилятора Rust входит функциональность называющаяся проверка заимствования (borrow checker), сравнивающая области видимости чтобы убедиться в корректности всех ссылок. Демонстрационный код 10-19 иллюстрирует всё тот же пример 10-18, графически изображая область действия имеющихся переменных:

{
    let r;         // -------+-- 'a
                   //        |
    {              //        |
        let x = 5; // -+-----+-- 'b
        r = &x;    //  |     |
    }              // -+     |
                   //        |
    println!("r: {}", r); // |
                   //        |
                   // -------+
}

Пример 10-19: Описание времён жизни переменных r и x, с помощью идентификаторов 'a и 'b

Мы описываем время жизни переменной r с помощью 'a и время жизни переменной x с помощью описательной переменной 'b. Обратите внимание, что блок 'b находится внутри блока 'a и значительно меньше. Во время компиляции, компилятор Rust сравнивает размеры времён жизни и видит, что переменная r имеет время жизни 'a, но ссылается на объект с временем жизни 'b. Такая программа не скомпилируется потому что время жизни 'b короче, чем время жизни 'a. Ссылка, указывающая на данные живёт дольше, чем сами данные.

Рассмотрим другой пример (10-20), в котором нет проблем с недействительными ссылками.


# #![allow(unused_variables)]
#fn main() {
{
    let x = 5;            // -----+-- 'b
                          //      |
    let r = &x;           // --+--+-- 'a
                          //   |  |
    println!("r: {}", r); //   |  |
                          // --+  |
}                         // -----+
#}

Пример 10-20: Все ссылки действительны, поскольку данные имеют большее время жизни, чем ссылка

Здесь переменная x имеет время жизни 'b, которое больше, чем время жизни 'a. Это означает, что переменная r может ссылаться на переменную x и её значение будет действительно до конца блока, в котором была объявлена переменная x.

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

Обобщенные времена жизни в функциях

Напишем функцию, которая возвращает наибольшую по длине строку. Эта функция должна получать два строковых среза в качестве параметров функции и возвращать строковый срез в качестве результата. Код в примере 10-21 должен напечатать The longest string is abcd.

Файл: src/main.rs

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

Пример 10-21: Функция main вызывает функцию longest для поиска наибольшей строки

Обратите внимание, что мы хотим чтобы функция принимала в качестве параметров срезы строк (которые являются ссылками, об этом мы говорили в Главе 4) потому что мы не хотим чтобы функция longest принимала владение передаваемых аргументов. Мы хотим, чтобы функция могла принимать в качестве аргументов срезы от строк типа String и строковые литералы (переменная string1 имеет тип String, а string2 — строковый литерал).

Освежим в памяти материал Главы 4 (секцию «Срезы строк в качестве аргументов») для того, чтобы понять почему мы хотим использовать именно такие аргументы.

Если мы попробуем реализовать функцию longest так, как это показано в примере кода 10-22, то программа не будет скомпилирована:

Файл: src/main.rs

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Пример 10-22: Реализация функции longest, которая возвращает наибольший срез строки, но пока ещё не компилируется

Вместо этого мы получим следующую ошибку, сообщающую о проблемах в определении времени жизни возвращаемого параметра:

error[E0106]: missing lifetime specifier
   |
1  | fn longest(x: &str, y: &str) -> &str {
   |                                 ^ expected lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but the
   signature does not say whether it is borrowed from `x` or `y`

Справочный текст сообщает нам о том, что возвращаемому параметру нужно указать параметр времени жизни, потому что Rust не может определить на какую переменную ссылается возвращаемая ссылка, на x или y. На самом деле, даже мы не знаем что вернёт тело этой функции: ссылку на x или y.

Мы определили функцию longest таким образом, что мы не знаем конкретных значений, которые в неё передаются. Поэтому мы не знаем какая из ветвей оператора if будет выполнена. Мы также не знаем конкретных времён жизни ссылок, передаваемых в функцию, из-за чего не можем посмотреть на их области видимости, как мы делали в примерах 10-19 и 10-20, чтобы убедиться что возвращаемая ссылка всегда действительна. Компилятор тоже не может нам помочь потому что он не знает как времена жизни переменных x и y соотносятся с временем жизни возвращаемого значения. Мы собираемся добавить обобщённый параметр времени жизни, который определит отношения между ссылками чтобы компилятор мог провести анализ ссылок с помощью проверки заимствования.

Синтаксис аннотаций времён жизни

Аннотации времён жизни не меняют продолжительность жизни ссылок. Функции могут принимать ссылки с любым временем жизни если в сигнатуре указан обобщённый параметр времени жизни по аналогии с тем, как функции принимают аргумент любого типа если в сигнатуре функции указан обобщённый (generic) параметр. Что аннотации времён жизни действительно делают — это определяют отношения множества ссылок между собой.

Аннотация времени жизни имеет немного необычный синтаксис: имена параметров времени жизни обязаны начинаться с апострофа '. Имена времён жизни обычно очень короткие и пишутся в нижнем регистре. Обычно по умолчанию большинство людей использует имя 'a. Аннотации параметров функции временем жизни следуют после символа & ссылочного типа данных и разделяются пробелом от названия типа данных ссылки.

Приведём несколько примеров: у нас есть ссылка на i32 без указания времени жизни, ссылка на i32, с временем жизни, имеющим имя 'a и изменяемая ссылка на i32, которая тоже имеет время жизни 'a.

&i32        // ссылка
&'a i32     // ссылка с явно указанным времени жизни
&'a mut i32 // изменяемая ссылка с явным временем жизни

Одна аннотация времени жизни сама по себе не имеет большого смысла, аннотации времени жизни сообщают компилятору Rust как обобщённые параметры времени жизни множества ссылок связаны между собой. Предположим что у нас есть функция с параметром first, имеющим ссылочный тип данных &i32 и время жизни 'a и вторым параметром second, который также имеет ссылочный тип &i32 с временем жизни 'a. Аннотации времени жизни этих параметров имеют одинаковое имя. Это показывает что ссылки first и second должны жить в программе одинаково долго.

Аннотации времён жизни в сигнатуре функции

Давайте посмотрим на аннотации времён жизни в контексте функции longest, над которой мы работаем. Обобщённые параметры времени жизни объявляются в треугольных скобках между именем функции и списком её параметров таким же образом, как и обобщённые типы данных. Ограничение для ссылок в параметрах и возвращаемом значении говорит о том, что все они имеют одинаковое время жизни 'a, добавленное к каждой ссылке как показано в примере 10-23.

Файл: src/main.rs


# #![allow(unused_variables)]
#fn main() {
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
#}

Пример 10-23: В сигнатуре функции longest указано что все ссылки должны иметь одинаковое время жизни — 'a

Теперь код 10-21, использующий эту функцию, может быть скомпилирован.

Сигнатура функции теперь говорит нам о том, что эта функция принимает два параметра с временем жизни 'a, которые имеют тип среза строки и живут так же долго, как время жизни 'a. Функция возвращает срез строки, который также будет жить не меньше, чем время жизни 'a. Это — контракт, которому Rust теперь должен следовать.

Определяя параметры времён жизни в этой функции, мы не меняем времена жизни значений, которые передаются в функцию или возвращаются из неё, но мы говорим что любые значения, которые не придерживаются этого контракта должны быть отклонены компилятором во время проверки заимствования. Эта функция не знает (и не должна знать) как долго живут переменные x и y, ей нужно только знать что есть некоторая область видимости, которая может быть заменена на 'a и которая будет удовлетворять сигнатуре функции.

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

Когда ссылки передаются в longest, их конкретные времена жизни, которые заменяются на 'a — это часть области видимости x, перекрывающейся с областью видимости y. Так как области видимости всегда вкладываются друг в друга, обобщённое время жизни 'a получит конкретное время жизни, равное наименьшему из времен жизни переменных x и y. Поскольку мы аннотировали возвращаемую ссылку таким же параметром временем жизни 'a, мы гарантировали что возвращаемая ссылка будет действительна так же долго, как кратчайшее из времён жизни x и y.

Давайте рассмотрим как это ограничивает использование функции longest, передав в неё ссылки с разными временами жизни, на простом примере 10-24. Переменная string1 действительна до конца внешней области видимости, string2 действительна до конца внутренней области видимости, а ссылка result ссылается на что-то, что действительно до конца внутренней области видимости. Этот код пройдёт проверку заимствования, скомпилируется и напечатает The longest string is long string is long.

Файл: src/main.rs

# fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
#     if x.len() > y.len() {
#         x
#     } else {
#         y
#     }
# }
#
fn main() {
    let string1 = String::from("long string is long");

    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("The longest string is {}", result);
    }
}

Пример 10-24: Использование функции longest со ссылками на значения типа String, которые имеют разное время жизни

А теперь рассмотрим пример, который покажет, что время жизни результата работы функции — минимальное из имеющихся. Мы переместим определение переменной result во внешнюю область видимости, но оставим присваивание значения к ней во внутренней области видимости. Потом мы переместим println!, использующий переменную result во внешнюю область видимости, после окончания внутренней. При таких условиях пример кода 10-25 не будет скомпилирован:

Файл: src/main.rs

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

Пример 10-25: Попытка использования переменной result после выхода string2 за пределы области видимости

Если мы попробуем скомпилировать этот код, то получим следующее сообщение:

error: `string2` does not live long enough
   |
6  |         result = longest(string1.as_str(), string2.as_str());
   |                                            ------- borrow occurs here
7  |     }
   |     ^ `string2` dropped here while still borrowed
8  |     println!("The longest string is {}", result);
9  | }
   | - borrowed value needs to live until here

Эта ошибка говорит о том, что если мы хотим использовать result в println!, переменная string2 должна жить до конца внешней области видимости. Rust знает об этом потому что мы аннотировали параметры функции и её возвращаемое значение одним временем жизни 'a.

Мы можем взглянуть на этот код и увидеть что string1 живёт дольше, таким образом result будет содержать ссылку на string1. Поскольку, string1 ещё не вышла из области видимости, ссылка на string1 будет действительна для println!. Однако, мы сообщили компилятору Rust что время жизни возвращаемой ссылки будет таким же, как наименьшее время жизни передаваемых в функцию ссылок. Поэтому компилятор отклонит код из примера 10-25 как содержащий недействительную ссылку.

Попробуйте поэкспериментировать с различными значениями и временами жизни, передаваемыми в функцию longest. Перед компиляцией делайте предположения о том, пройдёт ли ваш код проверку заимствования, затем проверяйте чтобы увидеть насколько вы были правы.

Мышление в терминах времён жизни

Правильный способ определения времён жизни зависит от того, что функция должна делать. Например, если мы изменим реализацию функции longest таким образом, чтобы она всегда возвращала свой первый аргумент вместо самого длинного среза строки — то указывать время жизни для параметра y нет необходимости. Этот код компилируется:

Файл: src/main.rs


# #![allow(unused_variables)]
#fn main() {
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x
}
#}

В этом примере мы указали параметр времени жизни 'a для параметра x и возвращаемого значения, но не для параметра y, поскольку параметр y никак не соотносится с параметром x и возвращаемым значением.

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

Файл: src/main.rs

fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}

Даже если мы укажем параметр время жизни возвращаемому значению, этот код не не будет скомпилирован, так как время жизни возвращаемого значения не будет связано с временем жизни какого-либо из параметров. Мы получим следующее сообщение об ошибке:

error: `result` does not live long enough
  |
3 |     result.as_str()
  |     ^^^^^^ does not live long enough
4 | }
  | - borrowed value only lives until here
  |
note: borrowed value must be valid for the lifetime 'a as defined on the block
at 1:44...
  |
1 | fn longest<'a>(x: &str, y: &str) -> &'a str {
  |                                             ^

Проблема здесь в том, что переменная result выходит из области видимости и удаляется в конце функции longest, а мы пытаемся вернуть ссылку на result. В этом случае нет способа указать параметр времени жизни, который бы изменил недействительную ссылку, и Rust не позволит нам её создать. Лучшим исправлением этой проблемы был бы возврат переменной с владением вместо ссылки.

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

Определение времён жизни при объявлении структур

До сих пор мы объявляли структуры, которые содержали нессылочные типы данных. Структуры могут содержать и ссылочные типы данных, но при этом необходимо добавить аннотацию времени жизни для каждой ссылки в определение структуры. Пример кода 10-26 описывает структуру ImportantExcerpt, содержащую срезы строковых данных:

Файл: src/main.rs

struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.')
        .next()
        .expect("Could not find a '.'");
    let i = ImportantExcerpt { part: first_sentence };
}

Пример 10-26: Структура, содержащая ссылку и определение времени жизни

У структуры имеется одно поле part, хранящее ссылку на срез строки. Мы должны объявить имя обобщённого параметра времени жизни в треугольных скобках после имени структуры чтобы иметь возможность использовать его внутри структуры, так же, как при работе с обобщёнными типами данных.

Функция main создаёт экземпляр структуры ImportantExcerpt, который содержит ссылку на первое предложение строки novel.

Правила неявного выведения времени жизни

В этом разделе мы узнаем что у каждой ссылки всегда есть время жизни и нам нужно указывать параметры времён жизни для функций или структур, которые эти ссылки используют. Однако, изучая Главу 4, в разделе «Строковые срезы» мы создали функцию, которая использует ссылки, но при этом компилятор для её работы не требует информации о времени жизни. Вот эта функция:

Файл: src/lib.rs


# #![allow(unused_variables)]
#fn main() {
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}
#}

Пример 10-27: Функция, которую мы определили в Главе 4, компилируется без описания времени жизни параметров несмотря на то, что входной и выходной параметры — ссылки

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

fn first_word<'a>(s: &'a str) -> &'a str {

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

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

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

Неявные правила не имеют четких ограничений. Эти правила выводятся на основе имеющихся данных в коде программы. Если этих данных будет недостаточно — компилятор выведет соответствующее сообщение и не скомпилирует код.

Сначала немного определений: Времена жизни, определённые для параметров функции или метода называются входными временами жизни (input lifetimes), а времена жизни возвращаемых значений — выходными временами жизни.

Теперь перейдём к правилам, которые использует компилятор чтобы выяснить какие времена жизни имеют ссылки когда нет явных аннотаций. Первое правило применяется к входным временам жизни, следующие два — к выходным. Если компилятор применил все три правила, и в результате всё ещё не может определить времена жизни ссылок, он остановит работу с ошибкой.

  1. Каждый параметр, являющийся ссылкой, получает свой собственный параметр времени жизни. Другими словами, функция с одним параметром получит один параметр времени жизни: fn foo<'a>(x: &'a i32). Функция с двумя аргументами получит два различных параметра времени жизни: fn foo<'a, 'b>(x: &'a i32, y: &'b i32), и так далее.

  2. Если существует один входной параметр времени жизни — он назначается всем выходным параметрам: fn foo<'a>(x: &'a i32) -> &'a i32.

  3. Если есть множество входных параметров времени жизни и один из них является ссылкой &self или &mut self (в случае если эта функция является методом структуры или перечисления) — то время жизни self будет назначено всем выходным параметрам метода. Это позволяет удобней писать методы.

Давайте представим что мы компилятор и применим эти правила чтобы вывести времена жизни ссылок в сигнатуре функции first_word из примера 10-27. Сигнатура этой функции начинается без объявления времён жизни:

fn first_word(s: &str) -> &str {

Теперь мы (в качестве компилятора) применим первое правило, утверждающее, что каждый параметр функции получает свой собственный параметр времени жизни. Как обычно, мы собираемся назвать его 'a. Теперь сигнатура выглядит так:

fn first_word<'a>(s: &'a str) -> &str {

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

fn first_word<'a>(s: &'a str) -> &'a str {

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

Давайте рассмотрим ещё один пример: заголовок функции longest, в котором нет параметров времени жизни из примера 10-22:

fn longest(x: &str, y: &str) -> &str {

Притворимся компилятором снова. Применим первое правило: каждому параметру назначается собственное время жизни. На этот раз у функции два параметра, поэтому мы объявляем два времени жизни:

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

Второе правило применять нельзя потому что в сигнатуре указано больше одного входного параметра. Смотрим третье правило. Оно также не применимо, так как longest — функция, а не метод, следовательно, в ней нет параметра self. Итак, мы дошли до третьего правила, но до сих пор не смогли вычислить время жизни выходного параметра. Нам остаётся только вывести ошибку компиляции.

Мы знаем, что третье правило применяется только к методам. Давайте рассмотрим времена жизни в этом контексте и поймём почему в сигнатурах методов часто нам не нужно аннотировать времена жизни.

Аннотация времён жизни в определении методов

Когда мы реализуем методы для структур с временами жизни, синтаксис аннотаций снова схож с аннотациями обобщенных типов данных, как было показано в примере 10-11. Место объявления времени жизни зависит от того, с чем оно связано — с полем структуры или с аргументами методов и возвращаемым значением.

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

В сигнатурах методов внутри блока impl ссылки могут быть связаны с временем жизни ссылок в полях структуры или быть независимыми. Вдобавок, правила неявного выведения времён жизни часто делают аннотации переменных времён жизни необязательными. Рассмотрим несколько примеров использования структуры ImportantExcerpt, которую мы определили в примере 10-26.

Здесь в методе level входной параметр — ссылка на self а возвращаемое значение — просто i32, не ссылка:


# #![allow(unused_variables)]
#fn main() {
# struct ImportantExcerpt<'a> {
#     part: &'a str,
# }
#
impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}
#}

Объявление параметра времени жизни находится после impl и используется после имени структуры, поскольку является частью типа этой структуры. Но нам не нужно аннотировать время жизни ссылки на self благодаря первому правилу неявного выведения времён жизни.

Пример применения третьего правила:


# #![allow(unused_variables)]
#fn main() {
# struct ImportantExcerpt<'a> {
#     part: &'a str,
# }
#
impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}
#}

В этом методе имеется два входных параметра, поэтому Rust применят первое правило и назначает каждому параметру своё время жизни. Далее, поскольку один из параметров — &self, возвращаемое значение получает время жизни переменой &self. Теперь все времена жизни выведены.

Статическое время жизни

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


# #![allow(unused_variables)]
#fn main() {
let s: &'static str = "I have a static lifetime.";
#}

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

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

Всё вместе: обобщенные типы, связывание с типажом и времена жизни

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


# #![allow(unused_variables)]
#fn main() {
use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
    where T: Display
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
#}

Это — функция longest из примера 10-23, возвращающая наибольшую из двух строк, но с дополнительным аргументом ann. Параметр ann имеет обобщённый тип, реализующий типаж Display. Содержание данной переменной будет напечатано перед сравнением длин строковых срезов. Переменные времени жизни — это разновидность обобщённого типа данных, поэтому они объявляются рядом с декларацией обобщённого типа в треугольных скобках после названия функции.

Итоги

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

Верите или нет, здесь есть ещё чему поучиться: В Главе 17 мы поговорим о типажах-объектах, ещё одном способе использовать типажи. Глава 19 охватывает более сложные сценарии использования аннотаций времён жизни. Глава 20 расскажет о некоторых расширенных возможностях системы типов. А в следующей главе давайте поговорим о том, как писать тесты в Rust чтобы мы могли убедиться в том, что наш код, использующий все эти концепции, работает так, как мы хотим.