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

据我了解,代码是有效的:一旦控制离开match子句,通过采用导致代码 mutating 的路径,就可以返回提到的借用v

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

4

3 回答 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>,所以即使在没有捕获引用的分支中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()
}
于 2015-03-06T20:44:31.577 回答
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]
}
于 2015-03-07T06:20:42.997 回答
1

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

您可以在此处检查“get”函数的类型签名。如您所见,“get”函数返回对请求的向量成员的借用引用(包装在 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);
}

在 Rust 中,类型系统编码了所有权的概念。变量 v 是向量的所有者。当我们引用 v 时,我们让该变量(在本例中为 x)借用它一段时间。就像你拥有一本书,你借给我,我就是在借这本书。

因此,当我尝试通过第二次调用 push 来修改向量时,我需要拥有它。但是 x 正在借用它。你不能修改你借给别人的东西。所以 Rust 会抛出一个错误。

这是我的成像方式:

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];
}
于 2015-03-06T20:56:51.123 回答