阐明在函数签名中将两个对不同范围的引用对象的引用绑定到相同生命周期的含义

Posted

技术标签:

【中文标题】阐明在函数签名中将两个对不同范围的引用对象的引用绑定到相同生命周期的含义【英文标题】:Clarify the meaning of binding two references to differently scoped referents to the same lifetime in a function signature 【发布时间】:2017-08-06 01:39:31 【问题描述】:

我一直在尝试了解 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() 只返回对两个 referenced 字符串中较短者的引用。 main() 传入两个字符串引用,它们的引用对象定义在不同的范围内。我使用了String::from(),因此引用没有静态生命周期。程序正确打印short。 Here is the example in the 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? 感觉有点不对劲,好像我们从min() 返回对b 的引用,那么显然该引用在main() 中的a 的生命周期内无效。

    该函数接受两个引用并承诺产生一个对str 的引用,该引用可以与a 和 @ 这两个引用中较短的一个一样长987654344@? 这可能行得通,因为ab 的引用在main() 的内部范围内仍然有效。

    完全不同的东西?

总而言之,当它们的引用在调用者的不同范围内定义时,我不明白将min() 的两个输入引用的生命周期绑定到同一生命周期意味着什么。

【问题讨论】:

【参考方案1】:

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

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

诀窍是调用者缩短了两个引用之一的生命周期以匹配min()s 函数签名。如果您有参考 &amp;'x T,您可以将其转换为 &amp;'y T iff '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));
    //                    ^^^^^^^^^^^^^^^^^^

这与称为 subtyping 的东西有关,您可以在 this excellent answer 中了解更多信息。

总结:调用者缩短一个生命周期以匹配函数签名,这样函数就可以假设两个引用具有相同的生命周期。

【讨论】:

很好的答案!谢谢,我会等到一天结束,看看是否还有其他人。 另一方面,我想知道我是否应该尝试向 Rustonomicon 提出 PR,并添加这个例子。你怎么看?这肯定会为我节省一些时间。 @EddBarrett:我认为维护者会很高兴有更多的贡献,尤其是初学者,因为初学者最适合指出对他们来说是什么障碍。你可能想先打开一个问题,讨论你关于开发这个隐蔽主题的想法:这样你就可以在不先投入太多时间的情况下说出它们,并且他们可以在你开始之前指导你的工作(也许他们宁愿有一个更高级示例的新章节?也许他们宁愿把它放在这个例子之前,但放在另一个之后?...)。 嗯,我的看法正好相反——我会添加一个答案。【参考方案2】:

除了@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 之前结束。

【讨论】:

谢谢!事实上,我认为 cb 在编译器眼中处于不同的范围内,因为每个 let 绑定都会创建一个新的隐式范围(根据 Rustonomicon)。【参考方案3】:

我要去找(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 都不同于借用的实际 ab 的生命周期,当然两者都必须比每个调用生成的生命周期更长。

(Playground)

【讨论】:

我对你的回答持观望态度(尽管我赞成它,因为借用了问题)。从被调用者的角度来看,我认为借用并不重要。因此,从被调用者的角度来看,答案是 (2) => 被调用者保证结果可以与'a 一样长,即缩短的生命周期。结果是否真的存在那么长......并不重要。不过,它不能再活了。 @MatthieuM。如果我们从被调用者的角度来看,只有一世,所以没什么好说的。 (至少从我的的角度来看!) 我想说的是,有两种方法来看待这个问题:(1)结果可以有的最长生命周期是多少? (2) 论据借用了多长时间?在我看来,OP 倾向于 (1) 而对 (2) 不感兴趣。当然,我可能错了,我不是读心术! 好吧,我把它读作“&amp;a&amp;b 是如何被压缩成一生的'a。也不是读心术! 这就是为什么我赞成你和卢卡斯的答案,你们都提出了完全有效的观点......我不知道 OP 想要哪个......也许 OP 实际上需要 两者 分,因为对于初学者来说这不是很明显的区别:)

以上是关于阐明在函数签名中将两个对不同范围的引用对象的引用绑定到相同生命周期的含义的主要内容,如果未能解决你的问题,请参考以下文章

高并发学习

PHP引用传值

C#委托和事件详解

C#事件与委托详解

我可以以任何方式从 OpenCV 对象取消引用中将数据分配给双重函数返回检索吗?

C#事件与委托详解精华 多看看