可变借用自动更改为不可变?

时间:2016-11-17 12:30:40

标签: rust

似乎u,一个可变的借用,在

中变为自动不可变的
let v = &*u;

uv都是不可变的借用引用,因此它们都是允许的。

use std::ascii::AsciiExt;

fn show(a: &str) {
    println!("a={}", a);
}

fn main() {
    let mut t = String::new();
    t.push('s');
    let u = &mut t;
    u.make_ascii_uppercase(); // u is really mutable here
    let v = &*u; // u became immutable to allow this?
    show(u); // both u and v are now accessible!
    show(v);
}

输出:

a=S
a=S

如果我尝试在

之后使用u作为可变借用
show(v);

编译器会记得那个

let v = &*u;

真的不被允许:

cannot borrow `*u` as mutable because it is also borrowed as immutable

这是一个错误还是真的有些"当不再需要可变性时,会自动将可变借用转换为不可变的"原理?我正在使用Rust 1.13.0。

3 个答案:

答案 0 :(得分:5)

一个可变的引用可以不可靠地借用,但这不是这里发生的事情。

在使用&形成引用时,您需要明确可变性;除非你指定&mut,否则它将是一个不可变的引用。

您的示例可以简化为:

use std::ascii::AsciiExt;

fn main() {
    let mut t = "s".to_string();
    let u = &mut t;
    u.make_ascii_uppercase();
    let v = &*u;

    let () = v;
}

最后一行是让编译器告诉我们(在错误消息中)v的类型是什么的技巧。它报道:

error[E0308]: mismatched types
 --> <anon>:9:9
  |
9 |     let () = v;
  |         ^^ expected reference, found ()
  |
  = note: expected type `&std::string::String`
  = note:    found type `()`

我们有:

  • u:一个不可变的绑定,是t的可变借用
  • v:一个不可变的绑定,是tu
  • 的不可变重新借用

但是,如果我将v行更改为let v = &mut *u;,那么我会获得expected type '&mut std::string::String',然后我们会:

  • u:一个不可变的绑定,是t的可变借用
  • v:一个不可变的绑定,是tu
  • 的可变重新借用

这里的重要概念是重新借用,这是&*u&mut *u的内容。重新借用允许从现有参考中形成新的参考:

  • 重新借用最初借用的变量
  • 在重新借用的生命周期中,借用其形成的参考

重新借用规则相对简单,它们反映了借用规则:

  • 如果从不可变引用开始:
    • 你可以重新借用它作为一个不可变的引用,如果你愿意,可以使用多个并发的不可变重新借用
  • 如果你从一个可变引用开始:
    • 您可以将其作为可变参考重新借用,仅限
    • 或者您可以将其作为不可变引用重新借用,如果您愿意,可以使用多个并发不可变的借用

值得注意的是,重新借用的引用可以比其形成的引用更长寿命:

fn main() {
    let mut t = "s".to_string();

    let v;
    {
        let u = &mut t;
        v = &mut *u;
    }

    v.make_ascii_uppercase();
    show(v);
}

这是确保您可以从函数返回引用所必需的;当然。

因此,最终,重新借用会被编译器追溯到原来的借用值;但是,由于重新借用机制,它允许形成对此原始值的不可变引用,即使可变引用在范围内......并且只需确保此可变引用在新的不可变引用的生命周期内不可用。

当一个函数接受引用时,编译器会自动在调用站点引入一个具有适当可变性的重新借用;这是show这里发生的事情:show(u)实际上是show(&*u),在函数调用期间形成了一个新的不可变引用。

答案 1 :(得分:0)

首先,u在任何时候都不可变,因为它是let u声明的,而不是let mut u。你可以改变它所指向的String的原因是它拥有一个可变的引用; make_ascii_uppercase()修改t

v也是不可变的(mut中没有let v),因此当您调用show()处理不可变引用时,不会违反借用规则 - 您可以一次执行多次不可变借用。

答案 2 :(得分:0)

这很令人困惑,所以让我们做一些实验。

您的代码编译:

let mut t = String::new();
t.push('s');
let u = &mut t;
u.make_ascii_uppercase(); // u is really mutable here
let v = &*u; // u became immutable to allow this?
show(u); // both u and v are now accessible!
show(v);

如果我们将let v行更改为:

,会发生什么情况
let v = &t;
  

错误[E0502]:不能将t借用为不可变因为它也被借用为可变

     

- &GT; :12:14

好的,这是不同的。这告诉我&*u,尽管与&t的类型相同,但却不一样;前者是(子)借用 u ,但后者试图重新借用t

让我们尝试不同的实验。将前一行放回去,但现在添加一些新内容:

let v = &*u;   // the original reborrow
let w = u;     // Try to move out of `u`
  

错误[E0502]:不能将t借用为不可变因为它也被借用为可变

     

- &GT; :12:14

啊哈!这证实v确实是从u借来的,而不是直接来自t

现在,在原文中,让我们通过u添加一个尝试突变到最后:

let mut t = String::new();
t.push('s');
let u = &mut t;
u.make_ascii_uppercase(); // u is really mutable here
let v = &*u; // u became immutable to allow this?
show(u); // both u and v are now accessible!
show(v);
u.make_ascii_uppercase();

现在我明白了:

  

错误[E0502]:不能将*u借用为可变因为它也被借用为不可变的

我认为这基本上解释了发生了什么:

  • u可疑地借用t。这会直接阻止t被访问。
  • v不可侵犯地借用u。这意味着u仍可以不可变地使用,但它不能被可变地使用或移出。{/ li>
  • 另一个关键是,如果项目的完整路径是可变的,则只能使用可变值。由于u存在v时无法可靠地借用*u,因此您也不能可变地使用var list = specs.Join(users, s => s.lastupdatedby, u => u.userid, (s, u) => new { specs = s, users = u }) .Select(x => new { specId = x.specs.specId, desc = x.specs.desc, createdby=x.specs.createdby, username=x.users.username }).ToString(); 。 (这最后一点略显手淫;我欢迎进一步澄清......)