【问题标题】:Why can't I call a method with a temporary value?为什么我不能调用具有临时值的方法?
【发布时间】:2019-01-17 06:04:17
【问题描述】:

我无法在下面的代码中调用Foo::new(words).split_first()

fn main() {
    let words = "Sometimes think, the greatest sorrow than older";
/*
    let foo = Foo::new(words);
    let first = foo.split_first();
*/

    let first = Foo::new(words).split_first();

    println!("{}", first);
}

struct Foo<'a> {
    part: &'a str,
}

impl<'a> Foo<'a> {

    fn split_first(&'a self) -> &'a str {
        self.part.split(',').next().expect("Could not find a ','")
    }

    fn new(s: &'a str) -> Self {
        Foo { part: s }
    }
}

编译器会给我一个错误信息

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:8:17
   |
8  |     let first = Foo::new(words).split_first();
   |                 ^^^^^^^^^^^^^^^              - temporary value is freed at the end of this statement
   |                 |
   |                 creates a temporary which is freed while still in use
9  | 
10 |     println!("{}", first);
   |                    ----- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

如果我先绑定Foo::new(words)的值,然后调用split_first方法就没有问题了。

这两种调用方法在直觉上应该是相同的,但在某种程度上是不同的。

【问题讨论】:

  • 编译器会直接告诉您问题所在:temporary value is freed at the end of this statementcreates a temporary which is freed while still in useborrow later used here。我认为错误信息很清楚。如果没有,请告诉我们您的理解是什么。
  • 我不认为重复适用,因为如果没有被错误的显式生命周期阻止,临时 可以毫无问题地删除。

标签: rust lifetime temporary


【解决方案1】:

简短回答:删除 split_firstself 参数的 'a 生命周期:fn split_first(&amp;self) -&gt; &amp;'a str (playground)。

长答案:

当你编写这段代码时:

struct Foo<'a> {
    part: &'a str,
}

impl<'a> Foo<'a> {
    fn new(s: &'a str) -> Self {
        Foo { part: s }
    }
}

您告诉编译器所有Foo 实例都与某个生命周期'a 相关,该生命周期必须等于或短于作为参数传递给Foo::new 的字符串的生命周期。 'a 的生命周期可能不同于每个 Foo 实例的生命周期。然后你写:

let words = "Sometimes think, the greatest sorrow than older";
Foo::new(words)

编译器推断'a 的生命周期必须等于或短于words 的生命周期。除非有任何其他约束,否则编译器将使用 words 的生命周期,即 'static,因此它在程序的整个生命周期内都有效。

当您添加split_first 的定义时:

fn split_first(&'a self) -> &'a str

您正在添加一个额外的约束:您是说'a 也必须等于或短于self 的生命周期。因此,编译器将采用words 的生命周期和临时Foo 实例的生命周期中较短的一个,即临时实例的生命周期。 @AndersKaseorg's answer 解释了为什么这不起作用。

通过删除self 参数上的'a 生命周期,我将'a 与临时生命周期解相关,因此编译器可以再次推断'awords 的生命周期,即足够长的时间让程序运行。

【讨论】:

  • 这是一个很好的观点。我从未检查过split_first 实际上 是否将引用返回到它的输入中,只是它的类型说它可以。修复类型是更好的解决方案。
【解决方案2】:

Foo::new(words).split_first() 将被大致解释为

let tmp = Foo::new(words);
let ret = tmp.split_first();
drop(tmp);
ret

如果 Rust 允许你这样做,ret 中的引用将指向 [编辑:split_first 的类型将允许指向*]到现在丢弃的tmp 的值。所以 Rust 不允许这样做是件好事。如果你用 C++ 编写等效的单行代码,你会默默地得到未定义的行为。

通过自己编写 let 绑定,您可以将删除延迟到范围结束,从而扩展可以安全地拥有这些引用的区域。

有关更多详细信息,请参阅 Rust 参考中的temporary lifetimes

* 编辑:作为pointed out by Jmb,这个特定示例中的真正问题是类型

fn split_first(&'a self) -> &'a str

不够具体,更好的解决方案是将类型细化为:

fn split_first<'b>(&'b self) -> &'a str

可以缩写:

fn split_first(&self) -> &'a str

这传达了预期的保证,即返回的引用不指向Foo&lt;'a&gt;(仅指向字符串本身)。

【讨论】:

  • @AndersKaseorg 我想阅读您提到的粗略解释是否有参考或文档?
  • @ÖmerErden 当然,我添加了一个链接。
  • 我仍然认为这是不正确的。 Rust does 允许您说过它不允许的示例。此外,在 C++ 中,这不应该是 UB,原因完全相同:因为引用的是 words,它确实活得足够长。
猜你喜欢
  • 2011-06-21
  • 2011-12-09
  • 2013-07-09
  • 1970-01-01
  • 2017-05-27
  • 1970-01-01
  • 2021-10-31
  • 2014-08-09
  • 2018-06-12
相关资源
最近更新 更多