匹配语句与显式返回借用引用

4

在了解Rust的过程中,我注意到了一个我不太理解的行为。

我有这样一段代码,它按预期工作:

fn get_or_create_foo(v: &mut Vec<String>) -> String {
    match v.get(0) {
        Some(x) => return x.clone(),
        None => ()
    }

    println!("creating foo");
    v.push("foo".to_string());
    v.get(0).unwrap().clone()
}

fn main() {
    let mut v = Vec::new();
    println!("{}", get_or_create_foo(&mut v));
    println!("{}", get_or_create_foo(&mut v));
}

当我将get_or_create_foo()修改为返回一个借用字符串切片时,编译器拒绝编译它。
fn get_or_create_foo(v: &mut Vec<String>) -> &str {
    match v.get(0) {
        Some(x) => return x,
        None => ()
    }

    println!("creating foo");
    v.push("foo".to_string());
    v.get(0).unwrap()
}

编译记录:

$ rustc --verbose src/main.rs
src/main.rs:8:5: 8:6 error: cannot borrow `*v` as mutable because it is also borrowed as immutable
src/main.rs:8     v.push("foo".to_string());
                  ^
src/main.rs:2:11: 2:12 note: previous borrow of `*v` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `*v` until the borrow ends
src/main.rs:2     match v.get(0) {
                        ^
src/main.rs:10:2: 10:2 note: previous borrow ends here
src/main.rs:1 fn get_or_create_foo(v: &mut Vec<String>) -> &str {
...
src/main.rs:10 }
               ^
error: aborting due to previous error

在我看来,这段代码是有效的:只要通过引导到代码突变v的路径离开match子句,就可以立即归还提到的借用。
我错了吗?有人能举个例子说明允许这样的代码会造成问题吗?
3个回答

2

我不确定,但我怀疑你的代码:

fn get_or_create_foo(v: &mut Vec<String>) -> &str {
    match v.get(0) {
        Some(x) => return x,
        None => ()
    }

    println!("creating foo");
    v.push("foo".to_string());
    v.get(0).unwrap()
}

编译器会将其转换为具有等效语法的内容,通过消除显式 return,就像这样:

fn get_or_create_foo(v: &mut Vec<String>) -> &str {
    match v.get(0) {
        Some(x) => x,
        None => {
            println!("creating foo");
            v.push("foo".to_string());
            v.get(0).unwrap()
        },
    }
}

很显然,使用相同的错误会导致失败。这里 get 会生成 Option<&String>,因此即使在 None 分支中,v 仍然被借用,而没有引用被捕获。

幸运的是,有一种简单的方法可以重写该函数:

fn get_or_create_foo(v: &mut Vec<String>) -> &str {
    if v.get(0).is_none() {
        println!("creating foo");
        v.push("foo".to_string());
    }

    v.get(0).unwrap()
}

2
您可以稍微改进swizard的解决方案:
fn get_or_create_foo(v: &mut Vec<String>) -> &str {
    if v.is_empty() {
        println!("creating foo");
        v.push("foo".to_string());        
    }

    &v[0]
}

1
我也是Rust的新手,但我认为我可能已经找到了你问题的根源。
你可以检查 "get" 函数的类型签名这里。正如你所看到的,"get" 函数返回一个借用引用,指向向量中请求的成员(包装在Option内)。我的猜测是,在你的情况下,编译器无法验证 "x" 是否能够从匹配块中 "逃脱"。
以下是来自Rust 30分钟入门的一个更简单但类似的示例:
fn main() {
   let mut v = vec![];

   v.push("Hello");

   let x = &v[0];

   v.push("world");

   println!("{}", x);
}

In Rust, the type system encodes the notion of ownership. The variable v is an owner of the vector. When we make a reference to v, we let that variable (in this case, x) borrow it for a while. Just like if you own a book, and you lend it to me, I'm borrowing the book.

So, when I try to modify the vector with the second call to push, I need to be owning it. But x is borrowing it. You can't modify something that you've lent to someone. And so Rust throws an error.

Here is how I am imaging it:

fn get_or_create_foo(v: &mut Vec<String>) -> &str {
    let a: &str;

    match v.get(0) {
        Some(x) => {
            a = x;
            return x;
        },
        None => ()
    }

    // Now "a" is still borrowing "v" immutably!
    // println!("{:?}", a);

    println!("creating foo");
    v.push("foo".to_string());
    v.get(0).unwrap()
}

我还是一个初学者,所以可能还有更多需要了解的地方。在尝试修改您的代码后,我得出了结论。

简单的重构可以解决这个问题:

fn get_or_create_foo(v: &mut Vec<String>) -> &str {
    match v.get(0) {
        // Notice how the borrowed value is never used and
        // thus can not "escape" our match block.
        Some(_) => (),
        _       => v.push("foo".to_string())
    }
    
    // No need to use "get" here since we are 100% sure that
    // the indexed vector contains at least one item.
    return &v[0];
}

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