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

Я пытался разобраться в модели заимствования и владения Rust.

Предположим, у нас есть следующий код:

fn main() {
    let a = String::from("short");
    {
        let b = String::from("a long long long string");
        println!("{}", min(&a, &b));
    }
}

fn min<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() < b.len() {
        return a;
    } else {
        return b;
    }
}

min() просто возвращает ссылку на более короткую из двух упомянутых строк. main() передает две строковые ссылки, ссылки на которые определены в разных областях. Я использовал String::from(), чтобы ссылки не имели статического времени жизни. Программа правильно печатает short. Вот пример из Rust Playground.

Если мы обратимся к Rustonomicon (который, я ценю, находится в стадии разработки) , нам говорят, что значение сигнатуры функции, например:

fn as_str<'a>(data: &'a u32) -> &'a str

означает функцию:

берет ссылку на u32 с некоторым временем жизни и обещает, что может создать ссылку на str, который может жить так же долго.

Теперь обратимся к подписи min() из моего примера:

fn min<'a>(a: &'a str, b: &'a str) -> &'a str

Это более популярно, поскольку:

  • У нас есть две входные ссылки.
  • Их референты определены в разных областях, что означает, что они действительны для разных сроков жизни (a действительно дольше).

Используя формулировку, аналогичную приведенной выше цитате, что означает сигнатура функции min()?

  1. Функция принимает две ссылки и обещает создать ссылку на str, которая может существовать до тех пор, пока референты a и b? Это почему-то кажется неправильным, как будто мы возвращаем ссылка на b из min(), тогда ясно, что эта ссылка недействительна в течение срока жизни a в main().

  2. Функция принимает две ссылки и обещает создать ссылку на str, который может существовать до тех пор, пока короче из двух референтов a и b? Это может сработать, поскольку оба референта a и b остаются действительными во внутренней области main().

  3. Что-то совсем другое?

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


person Edd Barrett    schedule 15.03.2017    source источник


Ответы (3)


Это (2): возвращенная ссылка существует до тех пор, пока время жизни ввода меньше.

Однако с точки зрения функции оба времени жизни ввода фактически одинаковы (оба равны 'a). Итак, учитывая, что переменная a из main() явно живет дольше, чем b, как это работает?

Хитрость в том, что вызывающий сокращает время жизни одной из двух ссылок, чтобы соответствовать сигнатуре функции min()s. Если у вас есть ссылка &'x T, вы можете преобразовать ее в &'y T iff 'x outlives 'y (также написано: 'x: 'y). Это имеет интуитивный смысл (мы можем сократить время жизни ссылки без плохих последствий). Компилятор выполняет это преобразование автоматически. Итак, представьте, что компилятор превращает ваш main() в:

let a = String::from("short");
{
    let b = String::from("a long long long string");

    // NOTE: this syntax is not valid Rust! 
    let a_ref: &'a_in_main str = &a;
    let b_ref: &'b_in_main str = &b;
    println!("{}", min(&a as &'b_in_main str, &b));
    //                    ^^^^^^^^^^^^^^^^^^
}

Это связано с чем-то, что называется подтипом, и вы можете узнать больше об этом в этом отличном ответе .

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

person Lukas Kalbertodt    schedule 15.03.2017
comment
Отличный ответ! Спасибо, я подожду до конца дня, чтобы посмотреть, не заговорит ли кто-нибудь еще. - person Edd Barrett; 15.03.2017
comment
С другой стороны, мне интересно, стоит ли мне попытаться поднять PR для Rustonomicon, добавив именно этот пример. Что вы думаете? Это наверняка сэкономило бы мне время. - person Edd Barrett; 15.03.2017
comment
@EddBarrett: Я думаю, что сопровождающие были бы рады получить больше предложений, особенно от новичков, поскольку новички лучше всего подходят, чтобы указать, что для них является препятствием. Вы можете сначала открыть вопрос, чтобы обсудить свою идею развития этой скрытой темы: так вы сможете озвучить их, не вкладывая слишком много времени, и они могут направить вашу работу, прежде чем вы начнете (возможно, они предпочтут новая глава для более сложного примера? Может, они предпочли бы поместить ее перед этим примером, но после этого другого? ...). - person Matthieu M.; 15.03.2017
comment
Хм, я наоборот вижу - добавлю ответ. - person Chris Emerson; 15.03.2017

Я собираюсь сделать (3) что-нибудь еще!

С вашей подписью функции:

fn min<'a>(a: &'a str, b: &'a str) -> &'a str { ...}

// ...
min(&a, &b)

'a - это не время жизни заимствованных объектов. Это новое время жизни, созданное компилятором только для этого вызова. a и b будут заимствованы (или, возможно, повторно заимствованы) на время, необходимое для вызова, с расширением области возвращаемого значения (поскольку оно ссылается на тот же 'a).

Некоторые примеры:

let mut a = String::from("short");
{
    let mut b = String::from("a long long long string");
    // a and b borrowed for the duration of the println!()
    println!("{}", min(&a, &b));
    // a and b borrowed for the duration of the expression, but not
    // later (since l is not a reference)
    let l = min(&a, &b).len();

    {
        // borrowed for s's scope
        let s = min(&a, &b);
        // Invalid: b is borrowed until s goes out of scope
        // b += "...";
    }
    b += "...";  // Ok: b is no longer borrowed.
    // Borrow a and b again to print:
    println!("{}", min(&a, &b));
}

Как вы можете видеть, 'a для любого отдельного вызова отличается от времени жизни фактических a и b, которые заимствованы, хотя, конечно, оба должны пережить сгенерированное время жизни каждого вызова.

(площадка)

person Chris Emerson    schedule 15.03.2017
comment
Я нахожусь на заборе в отношении вашего ответа (хотя я поддержал его, поскольку заимствования имеют значение). С точки зрения вызываемого, я бы сказал, что заимствование не имеет значения. И, таким образом, с точки зрения вызываемого, ответ: (2) = ›вызываемый гарантирует, что результат может существовать до тех пор, пока 'a, сокращенное время жизни. Действительно ли результат живет так долго или нет ... не имеет значения. Но жить дальше он не может. - person Matthieu M.; 15.03.2017
comment
@MatthieuM. Если мы посмотрим с точки зрения вызываемого, существует только одно время жизни, так что сказать особо нечего. (По крайней мере, с моей точки зрения!) - person Chris Emerson; 15.03.2017
comment
Я пытаюсь сказать, что есть два способа взглянуть на вопрос: (1) Какое максимальное время жизни может иметь результат? и (2) На какой срок заимствуются аргументы? Мне кажется, что OP стремится к (1), а не к (2). Я, конечно, могу ошибаться, я не умею читать мысли! - person Matthieu M.; 15.03.2017
comment
Хорошо, я прочитал это так, как &a и &b сжаты в одну жизнь 'a. Также не мыслящий! - person Chris Emerson; 15.03.2017
comment
Вот почему я поддержал и ваш, и ответы Лукаса, вы оба делаете полную действительную точку ... и я понятия не имею, что хотел OP ... и, возможно, OP действительно нуждается в обоих точках, потому что это не сразу видно есть разница для новичка :) - person Matthieu M.; 15.03.2017

Помимо того, что @Lukas упомянул в ответе, вы также можете прочитать подпись функции как - Возвращенная ссылка действительна до момента, когда обе переданные ссылки действительны, то есть это соединение (также известное как AND) между временем жизни параметров.

В этом есть кое-что еще. Ниже приведены два примера кода:

    let a = String::from("short");
    {
        let c: &str;
        let b = String::from("a long long long string");
        c = min(&a, &b);

    } 

А ТАКЖЕ

let a = String::from("short");
    {
        let b = String::from("a long long long string");
        let c: &str;
        c = min(&a, &b);

    }

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

person Ankur    schedule 15.03.2017
comment
Спасибо! Фактически, я думаю, что c и b находятся в разных областях видимости в глазах компилятора, поскольку каждая привязка let создает новую неявную область видимости (согласно Rustonomicon). - person Edd Barrett; 15.03.2017