Уточнить смысл связывания двух ссылок на ссылки разной области действия с одним и тем же временем жизни в сигнатуре функции
Я пытался разобраться в модели заимствования и владения 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()
имею в виду?
Функция принимает две ссылки и обещает создать ссылку на
str
которые могут жить так долго, как референтыa
а такжеb
? Это как-то неправильно, как будто мы возвращаем ссылку наb
отmin()
тогда ясно, что ссылка недействительнаa
вmain()
,The function accepts two references and promises to produce a reference to a
str
that can live as long as the shorter of the two referents ofa
а такжеb
? That could work, since both referents ofa
а такжеb
remain valid in the inner scope ofmain()
,Something else entirely?
To summarise, I don't understand what it means to bind the lifetimes of the two input references of min()
to the same lifetime when their referents are defined in different scopes in the caller.
3 ответа
Это (2): возвращаемая ссылка живет столько же, сколько и более короткое время жизни ввода.
Однако, с точки зрения функции, оба времени жизни входа на самом деле одинаковы (оба 'a
). Итак, учитывая, что переменная a
от main()
явно живет дольше, чем b
, как это работает?
Хитрость заключается в том, что вызывающая сторона сокращает время жизни одной из двух ссылок, чтобы соответствовать min()
подпись функции s. Если у вас есть ссылка &'x T
, вы можете преобразовать его в &'y T
тогда и только тогда 'x
переживет '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));
// ^^^^^^^^^^^^^^^^^^
}
Это связано с тем, что называется подтипом, и вы можете прочитать больше об этом в этом превосходном ответе.
Подводя итог: вызывающая сторона сокращает время жизни до соответствия сигнатуре функции, так что функция может просто предположить, что обе ссылки имеют одинаковое время жизни.
Я собираюсь пойти (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
которые заимствованы, хотя, конечно, оба должны пережить сгенерированное время жизни каждого вызова.
Помимо того, что @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
,