具有借用引用的显式 return 的匹配语句

Match statement with explicit return of a borrowed reference

在研究 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() 使其成为 return 借用的字符串切片时,编译器拒绝编译它。

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

根据我的理解,代码是有效的:一旦控制离开 match 子句,通过采取导致代码变异 v 的路径,提到的借用可能会被 returned .

我错了吗?当允许这样的代码会导致问题时,谁能举个例子?

我不太清楚,但我怀疑你的代码:

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>,因此 v 即使在 None 分支中仍然是借用的,其中没有捕获引用。

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

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()
}

我自己也是 Rust 新手,但我相信我可能已经找到问题的根源。

您可以检查“get”函数的类型签名here。如您所见,“获取”函数 returns 对所请求的向量成员的借用引用(包装在 Option 中)。我的猜测是编译器无法在您的情况下验证“x”无法从匹配块中“转义”。

这是一个更简单但类似的示例,来自 A 30-minute Introduction to Rust:

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.

这是我的成像方式:

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

您可以稍微改进 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]
}