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

问题描述 投票:1回答:1

[我正在研究Rust Book,对第4章中的清单感到惊讶:

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

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

为什么Rust不允许对可变变量进行不变的引用?这似乎会削弱安全保证。如果我有一个可变的变量,并且将不可变的引用传递给某些线程,则这些线程假定值不会更改,但是我可以通过原始变量来对该值进行更改。

我还没有到达线程,但是发现这个奇怪,在这种情况下,与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
}
rust
1个回答
3
投票

项目的可变性本质上是rust变量名称的一部分。以下面的代码为例:

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

[foo突然变得不可变,但这并不意味着前两个foo不存在。

另一方面,可变引用附加到对象的生存期,因此是类型绑定的,并且将存在其自身的生存期,如果没有访问原始对象,将不允许任何形式的访问通过参考。

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 lifetime: {:?}", foo);

my_string.push_str的第二次调用将不会编译,因为此后可以使用foo(在这种情况下,可以保证使用)。

您的特定问题要求类似以下内容,但您甚至不需要多线程即可对此进行测试:

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.如果您可以注释生命周期,它们看起来将类似于以下内容:

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!

一些补充说明:

  • 由于NLL,将编译以下代码:

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

    因为可变使用bar后未使用foo

  • 您提到线程,它有其自身的约束和特征:

    Send特性将使您可以跨线程授予变量的所有权。

    Send特性将允许您跨线程共享对变量的引用。只要原始线程在借用期间不使用该对象,这将包括可变引用。

    一些示例:

    • 类型SyncSync,它可以跨线程发送并在线程之间共享
    • 类型TSend + Sync,它可以在线程之间共享,但不能在线程之间发送。一个示例是只能在原始线程上销毁的窗口句柄。
    • 类型T!Send + Sync,它可以跨线程发送,但不能在线程之间共享。一个示例是T,由于它不使用原子(多线程安全组件),因此只能在单个线程上使用其运行时借用检查。
    • Send + !Sync类型为RefCell,它只能存在于创建它的线程上。一个示例是RefCell,它无法跨线程发送其自身的副本,因为它无法原子计数引用(请看T来做到这一点),并且由于它没有生命周期,因此无法在跨线程发送时强制其自身的单个副本存在线程边界,因此不能跨线程发送。
© www.soinside.com 2019 - 2024. All rights reserved.