【问题标题】:Mutable borrow automatically changes to immutable?可变借用自动更改为不可变?
【发布时间】:2016-11-17 12:30:40
【问题描述】:

似乎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。

【问题讨论】:

    标签: rust


    【解决方案1】:

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

    当使用& 形成引用时,您需要明确说明可变性;除非您指定 &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 = &amp;mut *u;,那么我得到expected type '&amp;mut std::string::String',然后我们有:

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

    这里的重要概念是重新借用,这就是&amp;*u&amp;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(&amp;*u),在函数调用期间形成了一个新的不可变引用。

    【讨论】:

    • 我还是不明白。通过调用 u.make_ascii_uppercase(); 我确信借用是可变的,否则编译器会抱怨。
    • @RomanPolach:这里有两个不同的东西。 (1) u 可变借用 t 和 (2) make_ascii_uppercase 可变借用 u 的裁判。 (1) 持续到函数结束,但是 (2) 仅持续函数调用期间(没有返回值来扩展它)。然后,在show(u) 行中,您有 (3) show 在函数调用期间不可变地借用u 的裁判。
    • 所以 u 在调用 make_ascii_uppercase 之前是可变借用,然后它是不可变借用,因为它是 used作为不可变的?这是我很好奇的事情。
    • 我也不确定我是否明白这一点。你是说u 尽管是&amp;mutt 的引用,但在传递给使用它的函数之前实际上并没有借用t
    • @RomanPolach: u 是一个不可变的绑定,它的值不能改变(它不能引用另一个变量),它的值是一个对t 的可变引用。 u 的类型不会改变,它始终是一个可变引用。但是,可以从u 借用(形成一个新的引用),这个新的引用可能是可变的或不可变的。
    【解决方案2】:

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

    您的代码编译:

    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 借为不可变,因为它也借为可变

    --> :12:14

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

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

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

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

    --> :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 仍然可以不可变地使用,但不能可变地使用或移出。
    • 另一个关键是,如果项目的完整路径是可变的,则只能使用可变值。由于u 不能在v 存在时可变地借用,所以您也不能可变地使用*u。 (这最后一点有点手忙脚乱;我欢迎进一步澄清......)

    【讨论】:

    • 我认为u 本身是借用的,而v 存在,因此必须遵守与正常相同的规则:如果v 是可变借用,则u 不能被重新借用完全借用,如果v是不可变借用,则只能形成其他不可变借用。
    • 最令人困惑的是绑定v时对u的副作用。在第一个代码示例中,编译器检查u&amp;*u 之后没有可变使用,并允许采用可变引用v。在最后一个代码示例中,它知道 u 确实用作可变引用,因此它不允许 &amp;*u...
    【解决方案3】:

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

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

    【讨论】:

    • 是的,可变和不可变我总是指借用/引用,而不是变量绑定。我试图描述的问题是我做了可变借用 (u),并且在某些情况下,借用变成了不可变借用(当仅用作不可变借用时)。
    猜你喜欢
    • 1970-01-01
    • 2016-05-02
    • 2018-05-17
    • 2018-07-05
    • 2017-05-02
    • 2018-04-15
    • 1970-01-01
    • 2015-09-14
    相关资源
    最近更新 更多