Когда полезно определить несколько времен жизни в структуре?

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

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

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

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

Когда это полезно? Может ли кто-нибудь предоставить пример кода, который не компилируется, когда оба времени жизни 'a но компилируется, когда времена жизни 'a а также 'b (или наоборот)?

4 ответа

Решение

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

static ZERO: i32 = 0;

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

fn get_x_or_zero_ref<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32 {
    if *x > *y {
        return x
    } else {
        return &ZERO
    }
}

fn main() {
    let x = 1;
    let v;
    {
        let y = 2;
        let f = Foo { x: &x, y: &y };
        v = get_x_or_zero_ref(&f.x, &f.y);
    }
    println!("{}", *v);
}

Если бы вы изменили определение Foo к этому:

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

Тогда код не скомпилируется.

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

Я хочу еще раз ответить на свой вопрос здесь, поскольку он все еще занимает высокие позиции в результатах поиска, и я чувствую, что могу лучше объяснить. Рассмотрим этот код:

Ржавчина Детская площадка

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

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

И ошибка:

      error[E0597]: `y` does not live long enough
--> src/main.rs:11:33
|
11 |         let f = Foo { x: &x, y: &y };
|                                 ^^ borrowed value does not live long enough
12 |         v = f.x;
13 |     }
|     - `y` dropped here while still borrowed
14 |     println!("{}", *v);
|                    -- borrow later used here

Что тут происходит?

  1. Срок службы требует, чтобы он был по крайней мере достаточно большим, чтобы охватить объем x до оператора (так как он инициализирован с помощью &x а затем назначен v).
  2. Определение указывает, что оба и используют одно и то же общее время жизни 'a, поэтому время жизни должно быть не меньше.
  3. Но это не сработает, потому что мы назначаем &yдо и выходит за рамки до. Ошибка!

Решение здесь - позволить Foo использовать отдельные времена жизни для и, что мы делаем с использованием нескольких общих параметров времени жизни:

Ржавчина Детская площадка

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

Теперь жизни и не связаны друг с другом. Компилятор по-прежнему будет использовать время жизни, действительное до тех пор, пока println! заявление для f.x. Но больше нет требования, которое использует то же время жизни, поэтому компилятор может выбрать меньшее время жизни для f.y, например тот, который действителен только для области y.

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

struct X<'a>(&'a i32);

struct Y<'a, 'b>(&'a X<'b>);

fn main() {
    let z = 100;
    //taking the inner field out of a temporary
    let z1 = ((Y(&X(&z))).0).0;  
    assert!(*z1 == z);
}

Структура Y имеет два параметра времени жизни, один для содержащегося в нем поля &Xи один для Xсодержащееся поле &z.

В операции ((Y(&X(&z))).0).0, X(&z)создается как временное и заимствовано. Его время жизни находится только в рамках этой операции и истекает в конце оператора. Но с тех порX(&z)время жизни отличается от содержащегося в нем поля &z, операцию можно вернуть &z, значение которого можно будет получить позже в функции.

Если используется одно время жизни для Yструктура. Эта операция не сработает, потому что время жизни&z то же самое, что и содержащая его структура X(&z), срок действия истекает в конце выписки; поэтому вернулся&z больше не действителен для последующего доступа.

Смотрите код на детской площадке.

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

      struct Container<'a> {
    data: &'a str,
}

struct Processor<'a, 'b> {
    container: &'a Container<'b>,
}

fn process<'a, 'b>(processor: &'a Processor<'a, 'b>) -> &'b str
where
    'a: 'b,
{
    processor.container.data
}

fn main() {
    let data = "Hello, world!";
    let container = Container { data: &data };
    let processor = Processor { container: &container };
    let result = process(&processor);
    println!("Result: {}", result);
}

Тип возвращаемого значения с более коротким сроком службы

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

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

Рассмотрим следующую структуру кода:

      {
    // Scope of item A
    {
        // Scope of item B
    }
}

Объект B может существовать только в пределах своей области действия, тогда как элемент A может существовать как в области действия элемента A, так и в области действия элемента B.

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

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

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

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

Это проблематично, поскольку мы указали тип возвращаемого значения, чтобы иметь время жизни., что соответствует области действия элемента B. Однако время жизни элемента A, представленное , превышает потребности возвращаемого типа. По сути, мы предоставляем возвращаемому типу билет, действительный в течение более короткого периода времени, чем фактический срок службы элемента A.

Чтобы решить эту проблему, нам нужно добавить ограничение на время существования элемента A, указывающее, что билеты, связанные сдействительны только в пределах области действия пункта B. Добавляя это ограничение, мы гарантируем, что возвращаемая ссылка не выйдет за пределы предполагаемой области действия и сохранит правильные отношения между сроками существования.

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

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

      static ZERO: i32 = 0;

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

// returning lifetime 'b
fn get_x_or_zero_ref<'a, 'b>(x: &'a i32, y: &'b i32) -> &'b i32
where
    // Adding constrain so a will not outlive b
    'a: 'b,
{
    if *x > *y {
        x
    } else {
        &ZERO
    }
}

fn main() {
    let x = 1;
    let v;
    {
        let y = 2;
        let f = Foo { x: &x, y: &y };
        v = get_x_or_zero_ref(&f.x, &f.y);
        println!("{}", *v);
    }
}

Другие вопросы по тегам