在循环中尝试更新Option<&str>时,出现“借用期间被丢弃的临时值”的错误提示。

24

我正在尝试实现一个常用模式 - 在下一次循环迭代中使用上一次循环迭代的结果。例如,实现需要提供上一页最后一个值的分页。

struct Result {
    str: String,
}    

fn main() {
    let times = 10;
    let mut last: Option<&str> = None;

    for i in 0..times {
        let current = do_something(last);
        last = match current {
            Some(r) => Some(&r.str.to_owned()),
            None => None,
        };
    }
}

fn do_something(o: Option<&str>) -> Option<Result> {
    Some(Result {
        str: "whatever string".to_string(),
    })
}

然而,我不确定如何从循环中实际获取值。目前,编译器报错为 temporary value dropped while borrowed(在&r.str.to_owned()处),尽管我做了许多其他的尝试,但都无济于事。

我找到实际解决问题的唯一方法是创建某种本地tmp_str变量并进行以下操作:

match current {
    Some(r) => {
        tmp_str.clone_from(&r.str);
        last = Some(&tmp_str);
    }
    None => {
        last = None;
    }
}

但那感觉好像不是应该这样做的方式。


我在stackoverflow上询问了一个简明的问题,关于Rust常见错误error[E0716]error E0716:temporary value dropped while borrowed (rust),它链接回了这个问题。 - JamesThomasMoon
当涉及到 #to_owned 时,这似乎是 Rust 的一个不必要的错误。 - Eric Walker
2个回答

15

在您的代码中,不清楚last: Option<&str>中引用的String的所有者应该是谁。您可以引入一个额外的可变局部变量,该变量拥有该字符串。但是这样您将有两个变量:所有者和引用,这似乎是冗余的。直接将last设置为所有者会更加简单:

struct MyRes {
    str: String,
}

fn main() {
    let times = 10;
    let mut last: Option<String> = None;

    for _i in 0..times {
        last = do_something(&last).map(|r| r.str);
    }
}

fn do_something(_o: &Option<String>) -> Option<MyRes> {
    Some(MyRes {
        str: "whatever string".to_string(),
    })
}

do_something函数中,你可以通过引用传递整个参数,这似乎更符合你想要的。还要注意,将自己的结构命名为Result是不好的想法,因为Result是编译器内置的一个普遍特性(例如?运算符等)。


后续问题:使用Option<&str>还是Option<String>

Option<&str>Option<String>都有不同的权衡取舍。一个更适合传递字符串字面量,另一个更适合传递拥有的String。实际上,我建议不使用任何一个,而是使函数对实现AsRef<str>的类型S泛型化。以下是各种方法的比较:

fn do_something(o: &Option<String>) {
    let _a: Option<&str> = o.as_ref().map(|r| &**r);
    let _b: Option<String> = o.clone();
}
fn do_something2(o: &Option<&str>) {
    let _a: Option<&str> = o.clone(); // do you need it?
    let _b: Option<String> = o.map(|r| r.to_string());
}
fn do_something3<S: AsRef<str>>(o: &Option<S>) {
    let _a: Option<&str> = o.as_ref().map(|s| s.as_ref());
    let _b: Option<String> = o.as_ref().map(|r| r.as_ref().to_string());
}

fn main() {
    let x: Option<String> = None;
    let y: Option<&str> = None;

    do_something(&x);                           // nice
    do_something(&y.map(|r| r.to_string()));    // awkward & expensive

    do_something2(&x.as_ref().map(|x| &**x));   // cheap but awkward
    do_something2(&y);                          // nice

    do_something3(&x);                          // nice
    do_something3(&y);                          // nice, in both cases
}
请注意,上述组合并非全部都很习惯用语,有些只是为了完整性而添加的(例如,请求 AsRef<str> 然后构建一个拥有的 String 看起来有点奇怪)。

1
据我所知 - 不会复制任何内容。实际上,只传递一个指针到 do_something,并且还保证 do_something 不会以任何方式修改 _o 的内容。如果必要,您可以通过使用 as_ref() 转换 Option<String>Option<&str>,然后进行强制转换的 map,如此处所述。我建议既不使用 &str 也不使用 String。我添加了各种方法的比较。 - Andrey Tyukin
3
据我理解,rustc 没有其他选择,只能将所有内容进行优化:Option正在执行一种称为 ["null pointer optimization"] 的操作,以便在此处消除所有与 Option 相关的内容。类型 S 在编译时被静态地推导出来,没有任何动态分发发生,因此在运行时实际上无法对其调用 map 方法的对象存在。因此,以上所有内容似乎都是与类型检查器纯粹的编译时游戏,它在编译后不留痕迹。 - Andrey Tyukin
1
Option<&str>Option<String>可以利用空指针优化,但是当您使用&Option<...>时,仍会产生双重间接成本。而且,当您手头有一个T时,调用一个接受&Option<T>的函数更加困难,因为您可能需要先移动T。出于这些原因,我会编写do_something3以接受Option<&S>而不是&Option<S>。然后,您可以调用do_something(x.as_ref())(如果您不想消耗String)或do_something(y)(因为&引用是Copy,所以您不需要非消耗版本)。 - trent
@ralh 上面评论的一个必然结果是,它实际上取决于您在 do_something 中使用值的方式;如果您要使用 String,则应该接受一个 String(或 S: Into<String>),并让调用者决定是否克隆 -- 对于调用者来说,这比接受一个引用再无条件复制更灵活。这就是我在答案中建议使用 Option<String> 的原因。有些情况下,您可能想要其中任何一种。 - trent
@trentcl 在 "it actually depends on what you're doing with the value inside do_something" - 这句话中,也许提出一个单独的问题并列出所有不同的变体,然后逐步解释在哪些情况下有用,最后将后续回答和评论讨论转换为一个大的单独回答,这样做是否有意义? - Andrey Tyukin
显示剩余3条评论

4

r.str.to_owned()是临时值。虽然你可以引用一个临时值,但由于临时值通常会在最内层封闭语句结束时被丢弃(销毁),因此在这一点上,该引用变得悬空。在这种情况下,“最内层封闭语句”可能是循环的最后一行,也可能是循环体本身——我不确定哪个适用于这里,但这并不重要,因为无论哪种方式,你都试图让last包含一个即将被丢弃的String的引用,使得last无法使用。编译器在下一次循环迭代中阻止你再次使用它是正确的。

最简单的解决方法就是根本不要将last作为引用——在这个例子中,这既不必要也不可取。只需使用Option<String>

fn main() {
    let times = 10;
    let mut last = None;

    for _ in 0..times {
        last = match do_something(last) {
            Some(r) => Some(r.str),
            None => None,
        };
    }
}

fn do_something(_: Option<String>) -> Option<Result> {
    // ...
}

还有一些方法可以使参考版本起作用,以下是其中之一:

let mut current;  // lift this declaration out of the loop so `current` will have
                  // a lifetime longer than one iteration
for _ in 0..times {
    current = do_something(last);
    last = match current {
        Some(ref r) => Some(&r.str),  // borrow from `current` in the loop instead
                                      // of from a newly created String
        None => None,
    };
}

如果你的代码比示例更复杂,使用 String 将意味着大量潜在昂贵的 .clone(),因此你可能希望这样做。


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