Почему нужны явные времена жизни в Rust?

Я читал часть жизни книги Rust, и я наткнулся на этот пример для именованного/явного времени жизни:

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

fn main() {
    let x;                    // -+ x goes into scope
                              //  |
    {                         //  |
        let y = &5;           // ---+ y goes into scope
        let f = Foo { x: y }; // ---+ f goes into scope
        x = &f.x;             //  | | error here
    }                         // ---+ f and y go out of scope
                              //  |
    println!("{}", x);        //  |
}                             // -+ x goes out of scope

Мне совершенно ясно, что ошибка, предотвращаемая компилятором, - это использование без ссылки ссылки, назначенной x: после выполнения внутренней области f и, следовательно, &f.x становятся недействительными, и не должен был быть назначен x.

Моя проблема в том, что проблему можно было легко проанализировать без использования явного времени жизни 'a, например, выведя незаконное назначение ссылки на более широкую область (x = &f.x;).

Итак, в каких случаях явные жизненные времена действительно необходимы для предотвращения ошибок после использования (или некоторых других классов?)?

Ответ 1

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

На самом деле это тот же вопрос, что "почему нужны явные типы, когда компилятор может их вывести". Гипотетический пример:

fn foo() -> _ {  
    ""
}

Конечно, компилятор может видеть, что я возвращаю &'static str, так зачем программисту его набирать?

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

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

Существует также преимущество в эффективности для компилятора - только функции сигнатур должны анализироваться для проверки типов и сроков жизни. Что еще более важно, он имеет преимущество в эффективности для программиста. Если у нас не было явного времени жизни, что делает эта функция:

fn foo(a: &u8, b: &u8) -> &u8

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

вызывая незаконное присвоение ссылки на более широкую область

Области - это время жизни, по существу. Чуть более четко, время жизни 'a является общим параметром lifetime, который может быть специализирован с определенной областью во время компиляции на основе сайта вызова.

- это явные времена жизни, необходимые для предотвращения ошибок [...]?

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

Ответ 2

Пример:

fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
    x
}

fn main() {
    let x = 12;
    let z: &u32 = {
        let y = 42;
        foo(&x, &y)
    };
}

Здесь важны явные времена жизни. Это компилируется, потому что результат foo имеет такое же время жизни, что и его первый аргумент ('a), поэтому он может пережить свой второй аргумент. Это выражается именами жизни в сигнатуре foo. Если вы переключили аргументы в вызове foo, компилятор будет жаловаться, что y не будет достаточно долго жить.

Ответ 3

Обратите внимание, что в этом фрагменте кода нет явных времен жизни, кроме определения структуры. Компилятор отлично способен вывести времена жизни в main().

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

struct RefPair(&u32, &u32);

Должны ли они быть разными сроками жизни или должны быть одинаковыми? Это имеет значение с точки зрения использования, struct RefPair<'a, 'b>(&'a u32, &'b u32) сильно отличается от struct RefPair<'a>(&'a u32, &'a u32). Теперь, для простых случаев, как и тот, который вы предоставили, компилятор теоретически может лишиться жизни, как это происходит в других местах, но такие случаи очень ограничены и не требуют дополнительной сложности в компиляторе, и это усиление в ясности будет очень наименее сомнительный.

Ответ 4

Случай из книги очень прост по дизайну - потому что тема сроков жизни считается сложной.

Помимо ответа Владимира Матвеева, компилятор не может легко определить время жизни в функции с несколькими аргументами.

Также у моего собственного optional ящик имеет тип OptionBool с методом as_slice, подпись которого на самом деле:

fn as_slice(&self) -> &'static [bool] { … }

Нет абсолютно никакого способа, чтобы компилятор мог понять, что из него.

Ответ 5

Аннотации времени жизни в следующей структуре:

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

указывает, что экземпляр Foo не должен пережить содержащуюся ссылку (поле x).

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

Лучшим примером может быть следующее:

fn main() {
    let f : Foo;
    {
        let y = &5;
        f = Foo { x: y };
    };
    println!("{}", f.x);
}

Теперь f действительно переживает переменную, на которую указывает f.x.

Ответ 6

Чтобы добавить к ответ Shepmaster:

Если функция получает две ссылки в качестве аргументов и возвращает ссылку, тогда реализация функции может иногда возвращать первую ссылку, а иногда и вторую. Невозможно предсказать, какая ссылка будет возвращена для данного вызова. В этом случае невозможно вывести время жизни для возвращаемой ссылки, поскольку каждая ссылка на аргумент может ссылаться на другую привязку переменной с другим временем жизни. Явные времена жизни помогают избежать/уточнить такую ​​ситуацию.

Аналогично, если структура содержит две ссылки (в виде двух полей-членов), то функция-член структуры может иногда возвращать первую ссылку, а иногда и вторую. Снова явные времена жизни предотвращают такие неоднозначности.

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

Ответ 7

Я нашел здесь еще одно замечательное объяснение: http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references.

В целом, можно возвращать ссылки только в том случае, если они полученный от параметра к процедуре. В этом случае указатель результат всегда будет иметь тот же срок службы, что и один из параметров; названные времена жизни указывают, какой параметр есть.