【问题标题】:Getting "temporary value dropped while borrowed" when trying to update an Option<&str> in a loop尝试在循环中更新 Option<&str> 时获得“借用时临时值下降”
【发布时间】:2019-06-26 12:57:36
【问题描述】:

我正在尝试实现一个常用的模式——在下一个循环迭代中使用上一个循环迭代的结果。例如,要实现分页,需要给出上一页最后一个值的 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&amp;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


    【解决方案1】:

    在您的代码中,尚不清楚last: Option&lt;&amp;str&gt; 中引用的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 是一个如此普遍的特征,深深地内置在编译器中(?-operator 等)。


    后续问题:Option&lt;&amp;str&gt;Option&lt;String&gt;

    Option&lt;&amp;str&gt;Option&lt;String&gt; 都有不同的权衡取舍。一种更适合传递字符串文字,另一种更适合传递拥有的Strings。我实际上建议两者都不使用,而是使函数泛型而不是实现AsRef&lt;str&gt; 的类型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&lt;str&gt; 然后构建一个拥有的 String 似乎有点奇怪)。

    【讨论】:

    • 感谢您的回答。我还有一些后续问题。据我了解,在函数参数中应该主要使用&amp;str 而不是String。当使用某种形式的Option&lt;String&gt; 时,最好的方式是绕过&amp;Option&lt;String&gt;?那么什么都不会被复制,对吧?而在你的回答中,只有last: Option&lt;MyRes&gt; 不是更好吗?除了我猜的内存使用量之外,它有什么缺点吗?是的,Result 显然是一个玩具示例,尽管确实可能是一个糟糕的示例。
    • @ralh 据我了解-不,没有任何内容被复制。本质上,只向do_something 传递了一个指针,同时也保证do_something 不会以任何方式修改_o 的内容。如果必须,您可以将Option&lt;String&gt; 转换为Option&lt;&amp;str&gt;,通过as_ref() 后跟带有强制转换的map,如explained here。我实际上建议既不使用&amp;str 也不使用String。我添加了各种方法的比较。
    • 再次感谢您非常详细的回答!我知道这里可能需要做一些 Rust 类型的魔法。我想知道 - AsRef 方法是否会增加任何开销(因为您正在调用 as_refmap...),还是 Rust 足够聪明,可以将这些开销编译掉?
    • @ralh 据我了解,rustc 除了优化它别无选择:Option 正在做一些名为"null pointer optimization" 的事情,所以所有与Option 相关的东西都是在这里被淘汰。 S 类型是在编译时静态推断的,在任何地方都不会发生动态分派,因此没有可以在运行时实际调用 map 的对象。所以,以上所有的一切似乎都是一个纯编译时的类型检查游戏,编译后没有留下任何痕迹。
    • Option&lt;&amp;str&gt;Option&lt;String&gt; 可以利用空指针优化,但是在使用&amp;Option&lt;...&gt; 时仍然会产生双重间接成本。当您手头有T 时,调用带有&amp;Option&lt;T&gt; 的函数会更加困难,因为您可能必须先移动T。出于这些原因,我会写do_something3 以使用Option&lt;&amp;S&gt; 而不是&amp;Option&lt;S&gt;。然后你可以调用do_something(x.as_ref())(如果你不想使用String)或do_something(y)(因为&amp;引用是Copy,你不需要非使用版本)。跨度>
    【解决方案2】:

    r.str.to_owned() 是一个临时值。 You can take a reference to a temporary,但是因为临时值通常会在最里面的封闭语句的末尾被删除(销毁),所以引用在那个点变得悬空。在这种情况下,“最里面的封闭语句”要么是循环的最后一行,要么是循环体本身——我不确定哪一个适用于此,但没关系,因为无论哪种方式,你都是试图使last 包含对很快将被删除的String 的引用,从而使last 无法使用。编译器阻止您在循环的下一次迭代中再次使用它是正确的。

    最简单的解决方法是根本不将last 设为引用——在示例中,这不是必要的或可取的。只需使用Option&lt;String&gt;

    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()s,您可能希望这样做。

    【讨论】:

      猜你喜欢
      • 2021-03-18
      • 1970-01-01
      • 2021-03-05
      • 2023-03-15
      • 1970-01-01
      • 2021-05-05
      • 2022-01-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多