尝试在循环中更新Option <&str>时获得“借入时暂时价值下降”

时间:2019-02-01 22:47:21

标签: loops rust borrow-checker

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

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; } } 变量,做一个黑客这样的:

{{1}}

但是那并不像它应该做的那样。

2 个答案:

答案 0 :(得分:3)

在您的代码中,尚不清楚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是一种深入渗透到编译器(?-operator等)中的普遍特征。


后续问题: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似乎有点奇怪)。

答案 1 :(得分:2)

r.str.to_owned()是一个临时值。 You can take a reference to a temporary,但由于通常会在最里面的封闭语句的末尾丢弃(破坏)该临时值,因此该引用在该点处变得悬而未决。在这种情况下,“最内层的语句”要么是循环的最后一行,要么是循环主体本身—我不确定确切适用于此的哪条语句,但这并不重要,因为无论哪种方式,尝试使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(),那么您可能想要这样做。