阐明在函数签名中将两个对不同作用域的引用绑定到同一生存期的含义



我一直在尝试了解 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的引用,该引用可以与ab的引用一样长。这感觉不知何故是错误的,就好像我们从min()返回对b的引用,那么很明显,该引用在main()a的生命周期内无效。

  2. 该函数接受两个引用,并承诺生成对str的引用,该引用的寿命与ab的两个引用中较短的一样长。这可以工作,因为ab的指称在main()的内部范围内仍然有效。

  3. 完全是别的什么?

总而言之,我不明白当调用方的不同范围中定义min()的两个输入引用的生命周期到同一生命周期意味着什么。

它是 (2):返回的引用寿命与较短的输入生存期一样长。

但是,从函数的角度来看,两个输入生存期实际上是相同的(两者都是'a)。因此,鉴于来自main()的变量a显然比b寿命更长,这是如何工作的?

诀窍是调用方缩短两个引用之一的生存期以匹配min()函数签名。如果你有一个引用&'x T,你可以把它转换成&'y Tiff'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不是借用对象的生存期。 它是编译器为此调用生成的新生存期。ab将在调用所需的时间内借用(或可能重新借用),并通过返回值的范围进行扩展(因为它引用相同的'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);
}

第一个不起作用(第二个不起作用)。看起来bc的生存期与它们在同一作用域中的生存期相同,但作用域中的排序也很重要,因为在第一种情况下b生存期将在c之前结束。

最新更新