错误[E0716]:在借用期间丢弃了临时值(Rust)

3

tl;dr error[E0716]: temporary value dropped while borrowed 是一个常见且难以解决的问题,是否有一致的解决方案?


我遇到了令人困扰的 rustc 错误。

error[E0716]: temporary value dropped while borrowed
...

creates a temporary which is freed while still in use

在Stackoverflow上搜索,有许多关于这个rust错误error[E0716]的问题。

也许一个rust专家可以提供一个通用解决方案来解决这个常见的新手问题,一个足够好的解决方案可能也可以回答下面链接中的问题。

示例代码

一个简洁的代码示例来演示问题 (rust playground):

type Vec1<'a> = Vec::<&'a String>;

fn fun1(s1: &String, v1: &mut Vec1) {
    v1.insert(0, &s1.clone());
}

fn main() {
    let mut vec1 = Vec::new();
    let str1 = String::new();
    fun1(&str1, &mut vec1);
}

结果:

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:4:19
  |
3 | fn fun1(s1: &String, v1: &mut Vec1) {
  |                      -- has type `&mut Vec<&'1 String>`
4 |     v1.insert(0, &s1.clone());
  |     --------------^^^^^^^^^^-- temporary value is freed at the end of this statement
  |     |             |
  |     |             creates a temporary which is freed while still in use
  |     argument requires that borrow lasts for `'1`

For more information about this error, try `rustc --explain E0716`.

我的理解

根据语句v1.insert(0, &s1.clone());
s1.clone()会创建一个新的String,并使用堆作为存储空间。然后将这个新克隆的String的引用(添加了&)传递给调用v1.insert的函数。因此,在函数fun1返回后,新的String数据和传递给insert的引用将保留。

但是,Rust编译器报告s1.clone()仅仅是临时的。

类似的链接问题

以下是类似问题的链接,版本不一定相同,但有点冗长(我个人认为)。

我在那些问题上添加了一条评论,其中链接到这个问题。

1
最好的建议是“在你使用 Rust 至少一年之前,不要使用 Lifetime”。 - Stargateur
3
请勿在其他问题的评论中垃圾广告,请仅在此帖子中提及它们以关联它们(位于右侧的“链接”部分)。 - kmdreko
2个回答

4

您的问题确实出现在编译所示的行。让我们稍微分析一下:

fn fun1(s1: &String, v1: &mut Vec1) {
    v1.insert(0, &s1.clone());
}

你的理解不太正确。让我们来看一下insert的签名:

pub fn insert(&mut self, index: usize, element: T)

类型T表示以值方式捕获,因此在调用后将不使用element。你只需通过使向量成为Vec<&String>而不是Vec<String>来微调此操作。

结果:您想要插入对字符串克隆的引用

如何做到这一点:克隆并插入引用

Rust引用和C/C++指针之间的区别在于,Rust不允许引用指向已分配的数据(它们必须始终指向有效数据)。当您克隆字符串时,您创建了一个副本,但该副本仅在fun1的生命周期内可用。当调用fun1结束时,克隆的字符串将被丢弃,因为函数拥有该克隆体,所以它负责清理它(Rust基于所有权的资源管理)。

在C++中,这将是有效的:您可以分配一个指针并将该指针推送到向量中,但是Rust不允许这样的事情。

您的fun1等效于:

fn fun1(s1: &String, v1: &mut Vec1) {
    let im_tmp = s1.clone();
    v1.insert(0, &im_tmp);
}

类似的操作应该引起警觉,因为im_tmp将被清除。要解决此问题:

type Vec1<'a> = Vec::<String>;

fn fun1(s1: &String, v1: &mut Vec1) {
    v1.insert(0, s1.clone());
}

fn main() {
    let mut vec1 = Vec::new();
    let str1 = String::new();
    fun1(&str1, &mut vec1);
    println!("{:?}", vec1);
}

类型不再是引用的向量,而是实际的对象。在 fun1 中,向量拥有克隆字符串的所有权。

另一个例子是您创建的向量类型,这个例子可以工作是因为编译器可以推断出克隆字符串的生命周期。请注意,如果您执行 vec1.insert(0, &str1.clone()) 将不起作用,因为对克隆的引用仅在调用 insert 的期间可用:

type Vec1<'a> = Vec::<&'a String>;

fn main() {
    let mut vec1 = Vec::new();
    let str1 = String::new();
    let clone = str1.clone();
    vec1.insert(0, &clone);
    println!("{:?}", vec1);
}

感谢 @AlexandruPlacinta。我最终使用了 String。我可能会研究一下 @NicholasWeston 的答案,他建议使用智能指针。在我的情况下,String 值永远不会改变,并且在程序的生命周期内保持不变,因此 Box 指针应该足够了。 - JamesThomasMoon

2
"s1.clone()可能在幕后涉及一些堆分配,但最终也涉及存储在栈上的内存。例如,字符串的长度和指针都存储在栈上。一旦方法退出,这些数据就会被丢弃。

如果Rust编译器没有阻止您这样做,v1将包含对s1.clone()创建的值的引用,该值将在方法存在后被丢弃。这是一个问题,因为v1将包含对不再有效的数据的引用。

回答您更一般的关于如何避免此错误(以及类似错误)的问题:

  • 一旦您具有显式生命周期参数-除非您有非常好的理由并确切地知道自己在做什么-否则您可能会走向一个兔子洞,并在借用检查器中无限循环。
  • 学习智能指针,这通常是在堆上分配值所必需的。

本文表达了类似的观点。

"

谢谢@NicholasWeston。在我这种情况下,String值永远不会改变,并将持续到程序的剩余生命周期。Vec<Box<String>>应该足够了。 - JamesThomasMoon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接