尝试循环更新选项时<&str>出现“借用时临时值丢失”的情况

问题描述 投票:0回答:3

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

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;
    }
}

但这感觉不像是应该的方式。

loops rust borrow-checker
3个回答
15
投票

在您的代码中,尚不清楚

String
中引用的
last: Option<&str>
的所有者应该是谁。您可以引入一个拥有该字符串的额外可变局部变量。但是这样你就会有两个变量:所有者和引用,这似乎是多余的。让
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
。我实际上建议两者都不使用,而是使该函数在实现
S
的类型
AsRef<str>
上通用。以下是各种方法的比较:

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
似乎有点奇怪)。


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()
,您可能需要这样做。


0
投票

Mara 博客中最近发表的有关此主题的文章可能很有用。 https://blog.m-ou.se/super-let/

© www.soinside.com 2019 - 2024. All rights reserved.