简体   繁体   English

为什么 Rust 允许对可变变量的不可变引用?

[英]Why Does Rust Allow an Immutable Reference to a Mutable Variable?

I'm working on the Rust Book (Chapter 4) and I am surprised that code like this compiles :我正在编写 Rust 书(第 4 章),我很惊讶这样的代码可以编译

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{}, {}", r1, r2);

    // this line silences the warning: 'variable does not need to be mutable'
    s.push_str(" world");
}

Why does Rust allow an immutable reference to a mutable variable?为什么 Rust 允许对可变变量的不可变引用? This would seem to weaken the safety guarantees.这似乎会削弱安全保障。 If I have a mutable variable, and I pass immutable references to some threads, those threads are assuming the value will not change, but I could mutate the value through the original variable.如果我有一个可变变量,并且我将不可变引用传递给某些线程,则这些线程假设该值不会改变,但我可以通过原始变量改变该值。

I haven't reached threading yet, but found this strange, and in this case, no different from C++:我还没有达到线程,但发现这很奇怪,在这种情况下,与 C++ 没有什么不同:

void doNotChangeMyString(const std::string& myConstString) {
  // ... assume that myConstString cannot change and use it on a thread
  // return immediately even though some worker thread is still
  // using myConstString
}

void main() {
    std::string s = "hello" // not const!
    doNotChangeMyString(s);
    s = "world"; // oops
}

Edit: I fixed the Rust code so that it compiles.编辑:我修复了 Rust 代码以便它编译。 Please reconsider the downvotes and close votes.请重新考虑反对票并关闭投票。 The accepted answer explains a concept that I did not get from Rust Book's chapter on borrowing, was very helpful to me, and could help others who are at the same point in learning Rust.接受的答案解释了一个我没有从 Rust 书中关于借用的章节中得到的概念,对我很有帮助,并且可以帮助其他在同一点学习 Rust 的人。

An item's mutability is essentially part of the name of the variable in rust.项目的可变性本质上是 rust 中变量名称的一部分。 Take for example this code:以这段代码为例:

let mut foo = String::new();
let foo = foo;
let mut foo = foo;

foo suddenly becomes immutable, but it does not mean that the first two foo s don't exist. foo突然变得不可变,但这并不意味着前两个foo不存在。

On the other hand, a mutable reference is attached to the lifetime of the object and is therefore type-bound, and will exist for its own lifetime, disallowing any kind of access to the original object if it is not through the reference.另一方面,可变引用附加到 object 的生命周期,因此是类型绑定的,并且将在其自己的生命周期内存在,如果不是通过引用,则不允许对原始 object 进行任何形式的访问。

let mut my_string = String::new();
my_string.push_str("This is ok! ");
let foo: &mut String = &mut my_string;
foo.push_str("This goes through the mutable reference, and is therefore ok! ");
my_string.push_str("This is not ok, and will not compile because `foo` still exists");
println!("We use foo here because of non lexical lifetimes: {:?}", foo);

The second call to my_string.push_str will not compile because foo can (in this case it is guaranteed to) be used afterwards.my_string.push_str的第二次调用将不会编译,因为foo可以(在这种情况下保证)在之后使用。

Your specific question asks something similar to the following, but you don't even need multithreading to test this:您的具体问题询问类似于以下内容,但您甚至不需要多线程来测试它:

fn immutably_use_value(x: &str) {
    println!("{:?}", x);
}

let mut foo = String::new();
let bar = &foo; //This now has immutable access to the mutable object.
let baz = &foo; //Two points are allowed to observe a value at the same time. (Ignoring `Sync`)
immutably_use_value(bar); //Ok, we can observe it immutably
foo.push_str("Hello world!"); //This would be ok... but we use the immutable references later!
immutably_use_value(baz);

This does not compile. 这不编译。 If you could annotate the lifetimes, they'd look something similar to this:如果您可以注释生命周期,它们看起来类似于以下内容:

let mut foo = String::new();  //Has lifetime 'foo
let bar: &'foo String = &foo; //Has lifetime 'bar: 'foo
let baz: &'foo String = &foo; //Has lifetime 'baz: 'foo
//On the other hand:
let mut foo = String::new();          //Has lifetime 'foo
let bar: &'foo mut String = &mut foo; //Has lifetime 'bar: mut 'foo
let baz: &'foo mut String = &mut foo; //Error, we cannot have overlapping mutable borrows for the same object!

A few extra notes:一些额外的注意事项:

  • Due to NLL (Non Lexical Lifetimes), the following code will compile:由于 NLL (Non Lexical Lifetimes),以下代码将编译:

     let mut foo = String::new(); let bar = &foo; foo.push_str("Abc");

    Because bar is not used after the mutable use of foo .因为在可变使用foo之后不使用bar

  • You mention threading, which has its own constraints and traits involved:你提到了线程,它有自己的约束和特征:

    The Send trait will allow you to give ownership of a variable across a thread. Send trait 将允许您跨线程授予变量的所有权。

    The Sync trait will allow you to share a reference to a variable across a thread. Sync trait 将允许您跨线程共享对变量的引用。 This includes mutable references, as long as the original thread does not use the object for the duration of the borrow.这包括可变引用,只要原始线程在借用期间不使用 object。

    A few examples:几个例子:

    • Type T is Send + Sync , it can be sent across threads and be shared between them Type TSend + Sync ,它可以跨线程发送并在它们之间共享
    • Type T is !Send + Sync , it can be shared across threads, but not sent between them. Type T!Send + Sync ,它可以跨线程共享,但不能在它们之间发送。 An example is a window handle that can only be destroyed on the original thread.一个示例是只能在原始线程上销毁的 window 句柄。
    • Type T is Send + !Sync , it can be sent across threads, but not shared between them. Type TSend + !Sync ,它可以跨线程发送,但不能在线程之间共享。 An example is RefCell , which will can only use its runtime borrow-checking on a single thread due to it not using atomics (Multithreading safe components).一个例子是RefCell ,由于它不使用原子(多线程安全组件),它只能在单个线程上使用其运行时借用检查。
    • Type T is !Send + !Sync , it can only live on the thread it was created on.类型T!Send + !Sync ,它只能存在于创建它的线程上。 An example is Rc , which cannot send a copy of itself across threads because it cannot count references atomically (Look at Arc to do that) and since it carries no lifetimes to force a single copy of itself to exist when sending across a thread boundary, it therefore cannot be sent across threads.一个例子是Rc ,它不能跨线程发送自身的副本,因为它不能以原子方式计算引用(查看Arc来做到这一点)并且因为它没有生命周期来强制在跨线程边界发送时存在自身的单个副本,因此它不能跨线程发送。
  • I use &str instead of &String in my third example, this is because String: Deref<str> (You may need to scroll down to see it), and therefore anywhere I need a &str I can chuck a &String in because the compiler will autoderef.我在第三个示例中使用&str而不是&String ,这是因为String: Deref<str> (您可能需要向下滚动才能看到它),因此在我需要&str的任何地方我都可以插入&String因为编译器会自动删除.

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Rust:如果不存在其他引用,则允许将不可变引用升级为可变引用 - Rust: allow upgrade of immutable reference to mutable reference, if no other references exist 为什么在将变量移动到 scope 后 Rust 编译器错误“不能作为不可变借用,因为它也作为可变借用”? - Why does the Rust compiler error with "cannot borrow as immutable because it is also borrowed as mutable" after moving the variable into a scope? 如果 Rust str 是不可变的,为什么我可以将 str 变量声明为可变的? - Why can I declare a str variable as mutable if Rust str is immutable? 为什么Rust允许使用不可变绑定通过引用字段进行突变? - Why does Rust allow mutation through a reference field using an immutable binding? 如何使 Rust 可变引用不可变? - How to make a Rust mutable reference immutable? 获取对不可变变量的可变引用? - Obtain a mutable reference to a immutable variable? 为什么 rust 在引用可变变量时重新声明可变性? - Why does rust re-declare mutability when taking a reference to a mutable variable? 为什么 Rust 不能在类型构造函数中强制可变引用到不可变引用? - Why Rust can't coerce mutable reference to immutable reference in a type constructor? 当 Rust 中的 function 需要可变引用时,为什么不可变引用可以匹配? - Why an immutable reference can work in match when mutable reference is required by function in Rust? 为什么使用Rust将可变结构传递给函数会导致字段不可变? - Why using Rust does passing a mutable struct to a function result in immutable fields?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM