尝试在循环中更新 Option<&str> 时获取“借用时丢失的临时值”

ral*_*alh 12 loops rust borrow-checker

我正在尝试实现一个常用的模式 - 在下一个循环迭代中使用上一个循环迭代的结果。例如,要实现分页,您需要给出上一页上最​​后一个值的 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(),
    })
}
Run Code Online (Sandbox Code Playgroud)

但是,我不确定如何真正从循环中获取值。目前,编译器错误是temporary value dropped while borrowed(at &r.str.to_owned()),虽然我做了很多其他尝试,但都无济于事。

我发现真正让它工作的唯一方法是创建某种局部tmp_str变量并像这样进行黑客攻击:

match current {
    Some(r) => {
        tmp_str.clone_from(&r.str);
        last = Some(&tmp_str);
    }
    None => {
        last = None;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

And*_*kin 10

在您的代码中,仍不清楚所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(),
    })
}
Run Code Online (Sandbox Code Playgroud)

在 中do_something,您可以通过引用传递整个参数,这似乎更可能是您想要的。另请注意,命名您自己的结构Result是一个坏主意,因为Result这种普遍存在的特性深深地内置于编译器(?-operator 等)中。


后续问题:Option<&str>Option<String>?

双方Option<&str>Option<String>有不同的取舍。一种更适合传递字符串文字,另一种更适合传递拥有的Strings。我实际上建议两者都不使用,而是使函数泛型超过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
}
Run Code Online (Sandbox Code Playgroud)

请注意,并非所有上述组合都非常惯用,添加一些只是为了完整性(例如,要求AsRef<str>然后构建一个拥有的Stringout 似乎有点奇怪)。

  • @ralh据我所知,rustc别无选择,只能将其全部优化:`Option`正在做一些称为[“空指针优化”](/sf/ask/3259032591/ is-the-the-null-pointer-optimization-in-rust),所以这里消除了所有与“选项”相关的东西。`S` 类型是在编译时静态推断的,任何地方都不会发生动态分派,因此没有可以在运行时实际调用 `map` 的对象。因此,以上所有内容似乎都是带有类型检查器的纯粹编译时游戏,编译后不会留下任何痕迹。 (2认同)