Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Валидация ссылок по времени жизни

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

One detail we didn’t discuss in the “References and Borrowing” section in Chapter 4 is that every reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, just like most of the time, types are inferred. We are only required to annotate types when multiple types are possible. In a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic lifetime parameters to ensure that the actual references used at runtime will definitely be valid.

Annotating lifetimes is not even a concept most other programming languages have, so this is going to feel unfamiliar. Although we won’t cover lifetimes in their entirety in this chapter, we’ll discuss common ways you might encounter lifetime syntax so that you can get comfortable with the concept.

Висячие ссылки

The main aim of lifetimes is to prevent dangling references, which, if they were allowed to exist, would cause a program to reference data other than the data it’s intended to reference. Consider the program in Listing 10-16, which has an outer scope and an inner scope.

fn main() {
    let r;

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

    println!("r: {r}");
}
Listing 10-16: An attempt to use a reference whose value has gone out of scope

Note: The examples in Listings 10-16, 10-17, and 10-23 declare variables without giving them an initial value, so the variable name exists in the outer scope. At first glance, this might appear to be in conflict with Rust having no null values. However, if we try to use a variable before giving it a value, we’ll get a compile-time error, which shows that indeed Rust does not allow null values.

The outer scope declares a variable named r with no initial value, and the inner scope declares a variable named x with the initial value of 5. Inside the inner scope, we attempt to set the value of r as a reference to x. Then, the inner scope ends, and we attempt to print the value in r. This code won’t compile, because the value that r is referring to has gone out of scope before we try to use it. Here is the error message:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `x` does not live long enough
 --> src/main.rs:6:13
  |
5 |         let x = 5;
  |             - binding `x` declared here
6 |         r = &x;
  |             ^^ borrowed value does not live long enough
7 |     }
  |     - `x` dropped here while still borrowed
8 |
9 |     println!("r: {r}");
  |                   - borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

The error message says that the variable x “does not live long enough.” The reason is that x will be out of scope when the inner scope ends on line 7. But r is still valid for the outer scope; because its scope is larger, we say that it “lives longer.” If Rust allowed this code to work, r would be referencing memory that was deallocated when x went out of scope, and anything we tried to do with r wouldn’t work correctly. So, how does Rust determine that this code is invalid? It uses a borrow checker.

Анализатор заимствований

Компилятор Rust включает в себя анализатор заимствований, который сравнивает области видимости для того, чтобы проверить, являются ли все заимствования действительными. В Листинге 10-17 показан тот же код, что и в Листинге 10-16, но с комментариями, показывающими времена жизни переменных.

fn main() {
    let r;                // ─────────┬── 'a
                          //          │
    {                     //          │
        let x = 5;        // ━┳━━ 'b  │
        r = &x;           //  ┃       │
    }                     // ━┛       │
                          //          │
    println!("r: {r}");   //          │
}                         // ─────────┘
Listing 10-17: Annotations of the lifetimes of r and x, named 'a and 'b, respectively

Here, we’ve annotated the lifetime of r with 'a and the lifetime of x with 'b. As you can see, the inner 'b block is much smaller than the outer 'a lifetime block. At compile time, Rust compares the size of the two lifetimes and sees that r has a lifetime of 'a but that it refers to memory with a lifetime of 'b. The program is rejected because 'b is shorter than 'a: The subject of the reference doesn’t live as long as the reference.

Listing 10-18 fixes the code so that it doesn’t have a dangling reference and it compiles without any errors.

fn main() {
    let x = 5;            // ━━━━━━━━━━┳━━ 'b
                          //           ┃
    let r = &x;           // ──┬── 'a  ┃
                          //   │       ┃
    println!("r: {r}");   //   │       ┃
                          // ──┘       ┃
}                         // ━━━━━━━━━━┛
Listing 10-18: A valid reference because the data has a longer lifetime than the reference

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

Now that you know where the lifetimes of references are and how Rust analyzes lifetimes to ensure that references will always be valid, let’s explore generic lifetimes in function parameters and return values.

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

We’ll write a function that returns the longer of two string slices. This function will take two string slices and return a single string slice. After we’ve implemented the longest function, the code in Listing 10-19 should print The longest string is abcd.

Filename: src/main.rs
fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("Самая длинная строка: {result}");
}
Listing 10-19: A main function that calls the longest function to find the longer of two string slices

Обратите внимание, что мы хотим, чтобы функция принимала строковые срезы (которые являются ссылками), а не сами строки, потому что мы не хотим, чтобы функция longest забирала во владение свои параметры. Обратитесь к разделу “Строковые срезы как параметры” Главы 4, чтобы вспомнить, почему параметры функции в Листинге 10-19 имеют именно такой тип.

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

Filename: src/main.rs
fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("Самая длинная строка: {result}");
}

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}
Listing 10-20: An implementation of the longest function that returns the longer of two string slices but does not yet compile

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

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0106]: missing lifetime specifier
 --> src/main.rs:9:33
  |
9 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named 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`
help: consider introducing a named lifetime parameter
  |
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

Текст ошибки показывает, что возвращаемому типу нужен обобщённый параметр времени жизни, потому что Rust не может определить, на что указывает возвращаемая ссылка — на x или на y. На самом деле, мы тоже не знаем! — блок if в теле функции возвращает ссылку на x, а блок else — на y.

When we’re defining this function, we don’t know the concrete values that will be passed into this function, so we don’t know whether the if case or the else case will execute. We also don’t know the concrete lifetimes of the references that will be passed in, so we can’t look at the scopes as we did in Listings 10-17 and 10-18 to determine whether the reference we return will always be valid. The borrow checker can’t determine this either, because it doesn’t know how the lifetimes of x and y relate to the lifetime of the return value. To fix this error, we’ll add generic lifetime parameters that define the relationship between the references so that the borrow checker can perform its analysis.

Аннотирование времени жизни

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

Lifetime annotations have a slightly unusual syntax: The names of lifetime parameters must start with an apostrophe (') and are usually all lowercase and very short, like generic types. Most people use the name 'a for the first lifetime annotation. We place lifetime parameter annotations after the & of a reference, using a space to separate the annotation from the reference’s type.

Here are some examples—a reference to an i32 without a lifetime parameter, a reference to an i32 that has a lifetime parameter named 'a, and a mutable reference to an i32 that also has the lifetime 'a:

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

One lifetime annotation by itself doesn’t have much meaning, because the annotations are meant to tell Rust how generic lifetime parameters of multiple references relate to each other. Let’s examine how the lifetime annotations relate to each other in the context of the longest function.

In Function Signatures

To use lifetime annotations in function signatures, we need to declare the generic lifetime parameters inside angle brackets between the function name and the parameter list, just as we did with generic type parameters.

We want the signature to express the following constraint: The returned reference will be valid as long as both of the parameters are valid. This is the relationship between lifetimes of the parameters and the return value. We’ll name the lifetime 'a and then add it to each reference, as shown in Listing 10-21.

Filename: src/main.rs
fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("Самая длинная строка: {result}");
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
Listing 10-21: The longest function definition specifying that all the references in the signature must have the same lifetime 'a

Теперь наша функция будет работать, а код из Листинга 10-19 — скомпилируется.

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

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

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

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

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

Filename: src/main.rs
fn main() {
    let string1 = String::from("длинная строка такая длинная");

    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("Самая длинная строка: {result}");
    }
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
Listing 10-22: Using the longest function with references to String values that have different concrete lifetimes

В этом примере переменная string1 действительна до конца внешней области видимости string2 действует до конца внутренней области видимости, а result ссылается на что-то, что является действительным до конца внутренней области видимости. Запустите этот код, и вы увидите что анализатор заимствований разрешает такой код; он скомпилируется и напечатает Самая длинная строка: длинная строка такая длинная.

Next, let’s try an example that shows that the lifetime of the reference in result must be the smaller lifetime of the two arguments. We’ll move the declaration of the result variable outside the inner scope but leave the assignment of the value to the result variable inside the scope with string2. Then, we’ll move the println! that uses result to outside the inner scope, after the inner scope has ended. The code in Listing 10-23 will not compile.

Filename: src/main.rs
fn main() {
    let string1 = String::from("длинная строка такая длинная");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("Самая длинная строка: {result}");
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
Listing 10-23: Attempting to use result after string2 has gone out of scope

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

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `string2` does not live long enough
 --> src/main.rs:6:44
  |
5 |         let string2 = String::from("xyz");
  |             ------- binding `string2` declared here
6 |         result = longest(string1.as_str(), string2.as_str());
  |                                            ^^^^^^^ borrowed value does not live long enough
7 |     }
  |     - `string2` dropped here while still borrowed
8 |     println!("The longest string is {result}");
  |                                      ------ borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

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

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

Try designing more experiments that vary the values and lifetimes of the references passed in to the longest function and how the returned reference is used. Make hypotheses about whether or not your experiments will pass the borrow checker before you compile; then, check to see if you’re right!

Relationships

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

Filename: src/main.rs
fn main() {
    let string1 = String::from("abcd");
    let string2 = "efghijklmnopqrstuvwxyz";

    let result = longest(string1.as_str(), string2);
    println!("Самая длинная строка: {result}");
}

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

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

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

Filename: src/main.rs
fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("Самая длинная строка: {result}");
}

fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("очень длинная строка");
    result.as_str()
}

Здесь, несмотря на то, что мы указали параметр времени жизни 'a для возвращаемого типа, реализация не будет пропущена анализатором, потому что время жизни возвращаемого значения никак не связано с временем жизни параметров. Мы получим такое сообщение об ошибке:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0515]: cannot return value referencing local variable `result`
  --> src/main.rs:11:5
   |
11 |     result.as_str()
   |     ------^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     `result` is borrowed here

For more information about this error, try `rustc --explain E0515`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

The problem is that result goes out of scope and gets cleaned up at the end of the longest function. We’re also trying to return a reference to result from the function. There is no way we can specify lifetime parameters that would change the dangling reference, and Rust won’t let us create a dangling reference. In this case, the best fix would be to return an owned data type rather than a reference so that the calling function is then responsible for cleaning up the value.

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

В определениях структур

So far, the structs we’ve defined all hold owned types. We can define structs to hold references, but in that case, we would need to add a lifetime annotation on every reference in the struct’s definition. Listing 10-24 has a struct named ImportantExcerpt that holds a string slice.

Filename: src/main.rs
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Зовите меня Измаил. Несколько лет тому назад...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}
Listing 10-24: A struct that holds a reference, requiring a lifetime annotation

This struct has the single field part that holds a string slice, which is a reference. As with generic data types, we declare the name of the generic lifetime parameter inside angle brackets after the name of the struct so that we can use the lifetime parameter in the body of the struct definition. This annotation means an instance of ImportantExcerpt can’t outlive the reference it holds in its part field.

Функция main создаёт экземпляр структуры ImportantExcerpt, который содержит ссылку на первое предложение строки String, принадлежащей переменной novel. Данные в novel существовали до создания экземпляра ImportantExcerpt. Кроме того, novel не может выйти из области видимости до тех пор, пока не выйдет ImportantExcerpt, поэтому ссылка внутри экземпляра ImportantExcerpt всегда остаётся действительной.

Неявный вывод времени жизни

Вы узнали, что у каждой ссылки есть время жизни и что нужно указывать параметры времени жизни для функций или структур, которые используют ссылки. Однако в Главе 4 у нас была функция в Листинге 4-9, которая затем была снова показана в Листинге 10-25: она компилировалась без аннотаций времени жизни.

Filename: src/lib.rs
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[..]
}

fn main() {
    let my_string = String::from("hello world");

    // first_word принимает срезы значений типа `String`
    let word = first_word(&my_string[..]);

    let my_string_literal = "hello world";

    // first_word принимает срезы строковых литералов
    let word = first_word(&my_string_literal[..]);

    // Поскольку строковые литералы *эквивалентны* срезам строк,
    // это тоже сработает, без необходимости брать срез!
    let word = first_word(my_string_literal);
}
Listing 10-25: A function we defined in Listing 4-9 that compiled without lifetime annotations, even though the parameter and return type are references

The reason this function compiles without lifetime annotations is historical: In early versions (pre-1.0) of Rust, this code wouldn’t have compiled, because every reference needed an explicit lifetime. At that time, the function signature would have been written like this:

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

After writing a lot of Rust code, the Rust team found that Rust programmers were entering the same lifetime annotations over and over in particular situations. These situations were predictable and followed a few deterministic patterns. The developers programmed these patterns into the compiler’s code so that the borrow checker could infer the lifetimes in these situations and wouldn’t need explicit annotations.

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

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

The elision rules don’t provide full inference. If there is still ambiguity about what lifetimes the references have after Rust applies the rules, the compiler won’t guess what the lifetime of the remaining references should be. Instead of guessing, the compiler will give you an error that you can resolve by adding the lifetime annotations.

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

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

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

Второе правило говорит, что если есть ровно один параметр времени жизни ввода, то его время жизни назначается всем параметрам времени жизни вывода: fn foo<'a>(x: &'a i32) -> &'a i32.

Третье правило говорит, что если есть множество параметров времени жизни ввода, но один из них является &self или &mut self, так как эта функция является методом, то время жизни self назначается временем жизни всем параметрам времени жизни вывода. Это третье правило делает методы намного приятнее для чтения и записи, потому что требуется меньше символов.

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

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

Then, the compiler applies the first rule, which specifies that each parameter gets its own lifetime. We’ll call it 'a as usual, so now the signature is this:

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

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

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

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

Давайте рассмотрим ещё один пример: на этот раз, функцию longest, в которой не было параметров времени жизни, когда мы начали с ней работать в Листинге 10-20:

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

Let’s apply the first rule: Each parameter gets its own lifetime. This time we have two parameters instead of one, so we have two lifetimes:

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

You can see that the second rule doesn’t apply, because there is more than one input lifetime. The third rule doesn’t apply either, because longest is a function rather than a method, so none of the parameters are self. After working through all three rules, we still haven’t figured out what the return type’s lifetime is. This is why we got an error trying to compile the code in Listing 10-20: The compiler worked through the lifetime elision rules but still couldn’t figure out all the lifetimes of the references in the signature.

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

В определении методов

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

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

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

First, we’ll use a method named level whose only parameter is a reference to self and whose return value is an i32, which is not a reference to anything:

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

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Пожалуйста, обратите внимание: {announcement}");
        self.part
    }
}

fn main() {
    let novel = String::from("Зовите меня Измаил. Несколько лет тому назад...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

The lifetime parameter declaration after impl and its use after the type name are required, but because of the first elision rule, we’re not required to annotate the lifetime of the reference to self.

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

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

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Пожалуйста, обратите внимание: {announcement}");
        self.part
    }
}

fn main() {
    let novel = String::from("Зовите меня Измаил. Несколько лет тому назад...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

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

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

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

#![allow(unused)]
fn main() {
let s: &'static str = "Я буду здесь всё время.";
}

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

You might see suggestions in error messages to use the 'static lifetime. But before specifying 'static as the lifetime for a reference, think about whether or not the reference you have actually lives the entire lifetime of your program, and whether you want it to. Most of the time, an error message suggesting the 'static lifetime results from attempting to create a dangling reference or a mismatch of the available lifetimes. In such cases, the solution is to fix those problems, not to specify the 'static lifetime.

Generic Type Parameters, Trait Bounds, and Lifetimes

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

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

    let result = longest_with_an_announcement(
        string1.as_str(),
        string2,
        "Сегодня кто-то — именинник!",
    );
    println!("Самая длинная строка: {result}");
}

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!("Внимание! {ann}");
    if x.len() > y.len() { x } else { y }
}

Это — функция longest из Листинга 10-21, которая возвращает наибольший из двух строковых срезов. Но теперь у неё есть дополнительный параметр с именем ann обобщённого типа T, который может быть представлен любым типом, реализующим трейт Display, как указано за where. Этот дополнительный параметр будет печататься с использованием {}, b необходимо ограничение по трейту Display. Поскольку времена жизни являются обобщениями, то объявления параметра времени жизни 'a и параметра обобщённого типа T помещаются в один список внутри угловых скобок после имени функции.

Подведём итоги

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

Believe it or not, there is much more to learn on the topics we discussed in this chapter: Chapter 18 discusses trait objects, which are another way to use traits. There are also more complex scenarios involving lifetime annotations that you will only need in very advanced scenarios; for those, you should read the Rust Reference. But next, you’ll learn how to write tests in Rust so that you can make sure your code is working the way it should.