在Rust中,将可变引用传递到函数的机制是什么?

3

我只是一个 Rust 的新手,对这一点感到有些困惑。举个例子:

fn func(s_ref: &mut str) {}

fn main() {
    let mut word = "hello".to_string();
    let s: &mut str = &mut word;
    func(s);
}

在讨论Rust之前,我想先谈一下C++,以更好地阐明Rust。

所以,在C++中,如果我们将一个指针s作为参数命名为s_ref传递给函数,那么指针本身将被复制为内部使用的临时变量,并且当它到达作用域的末尾时,s_ref将自动释放,对吗?

当涉及到Rust时,我认为情况是相同的。因为引用(或指针)是一个简单的数据类型(如i32),所以传递的引用s将生成一个名为s_ref的副本。但是如果这样做,就会有两个可变引用指向同一个变量word,违反了Rust定义的规则。

我知道我犯了一些错误,但我无法确定具体是哪里出了问题。你能帮忙吗?非常感谢。


1
请参阅可变引用是否具有移动语义? - Sven Marnach
@SvenMarnach 谢谢。这很有启发性。 - Eason
2个回答

9

你很接近,但你错过了一个关键细节。

在Rust中,赋值(包括函数参数的赋值)通过移动该值进行,除非该值的类型实现了Copy,此时会复制该值。在C++中,您必须使用std::move()请求移动(在不是隐式情况下),而在Rust中,对于无法复制的值,移动是自动的,移动会使原变量为空,尝试再次读取会导致编译时错误。

Copy特质被所有共享引用(&T)实现,但不实现可变引用(&mut T

但是,在这种特殊情况下,编译器做了一件聪明的事情。如果移动了引用,则在调用func()后,s将处于“移动状态”,因此您可能会认为不能再使用它...但您可以!

func(s);
func(s);

这个可以编译。那么发生了什么?

编译器在此处插入了一个重新借用,就好像你写了这个一样:

func(&mut *s);

这种结构要求编译器消耗引用s,而是重新借用它的指向。 (当将引用传递给函数时,会隐式发生这种情况,但在其他情况下,它是隐式的,必须显式地重新借用。)

但如果这样做,就会有两个可变引用指向同一个变量word,违反了Rust定义的规则。

啊,但这并不是完全的规则。规则是,在任何给定的时刻,一个值可以是:

  • 可以被任意数量的名称读取,或者
  • 最多只能由一个名称写入。

这并不意味着您不能同时拥有两个可变引用指向同一个值,只是意味着一次只能使用一个。

在你的代码中,main() 在重新借用 s 后将控制权转移到 func()。这意味着在 s_ref 消失之前,s 不能被使用 -- 但是在 s_ref 消失之前它根本就不可能被使用,因为必须在 func() 返回之后才能再次使用 s
因此,正如您所看到的,这并不违反 Rust 的别名规则。当您调用 func() 时,可以使用 s_ref 写入值 word,但在函数返回之前不能使用 s 写入值。然后,s 可以再次使用。
我们可以通过以下两个程序来演示这一点:
fn func(s_ref: &mut str) {}

fn main() {
    let mut word = "hello".to_string();
    let s: &mut str = &mut word;
    
    let s2: &mut str = &mut *s;
    func(s2);
    
    func(s);
}

这个编译通过了!我们重新借用ss2,但在停止使用s2之前我们不使用s。编译器会自动解决这个问题(参见非词法生命周期)。

然而,如果我们交错使用它们,就会出现问题:

fn func(s_ref: &mut str) {}

fn main() {
    let mut word = "hello".to_string();
    let s: &mut str = &mut word;
    let s2: &mut str = &mut *s;
    
    func(s);
    func(s2);
}

现在编译器会抱怨:

error[E0499]: cannot borrow `*s` as mutable more than once at a time
 --> src/main.rs:8:10
  |
6 |     let s2: &mut str = &mut *s;
  |                        ------- first mutable borrow occurs here
7 |     
8 |     func(s);
  |          ^ second mutable borrow occurs here
9 |     func(s2);
  |          -- first borrow later used here

在第 6 行和第 9 行之间,ss2 都被视为可用的,这是不允许的。
正如您所看到的,有多个可变引用指向同一值并没有问题。但是,它们的使用不允许重叠。只要它们不重叠,就不会出现问题。

太不可思议了!您的解释非常清晰易懂,即使我还没有学习再借用机制,我仍然比问题本身更理解了。非常感谢! - Eason

2
但如果这样做,就会有两个可变引用指向同一个变量word,违反了Rust的定义规则。
这并不是这样,因为借用是嵌套的,所以s_ref被视为s的子借用:只要s_ref存在,s就不能使用。
如果您从func返回s_ref并尝试访问它和原始值(或包括一些在打印s后使用返回的s_ref的代码,以确保它不会被丢弃),则可以看到这一点。
fn func(s_ref: &mut str) -> &mut str {s_ref}

fn main() {
    let mut word = "hello".to_string();
    let s: &mut str = &mut word;
    let u = func(s);
    println!("{s} {u}"); // cannot borrow `s` as immutable because it is also borrowed as mutable
}

非常感谢您!"sub-borrow"是否等同于"reborrow"机制? - Eason

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