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

为什么Rust允许对可变变量的不可变引用?,rust,Rust,我正在编写铁锈书(第4章),我很惊讶这样的代码: fn main(){ 让mut s=String::from(“hello”); 设r1=&s; 设r2=&s; println!(“{},{}”,r1,r2); //这一行消除了警告:“变量不需要是可变的” s、 push_str(“世界”); } 为什么Rust允许对可变变量的不可变引用?这似乎会削弱安全保障。如果我有一个可变变量,并且我将不可变的引用传递给一些线程,那么这些线程假设值不会改变,但是我可以通过原始变量改变值 我还没有接触到线

我正在编写铁锈书(第4章),我很惊讶这样的代码:

fn main(){
让mut s=String::from(“hello”);
设r1=&s;
设r2=&s;
println!(“{},{}”,r1,r2);
//这一行消除了警告:“变量不需要是可变的”
s、 push_str(“世界”);
}
为什么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 Book关于借阅的章节中得到这个概念,它对我非常有帮助,并且可以帮助其他在学习Rust方面处于相同地位的人。

一个项目的可变性本质上是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 lifetimes: {:?}", foo);
my\u string.push\u 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);
如果你能对生命周期进行注释,它们看起来会像这样:

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");
    
    因为在可变使用
    foo
    之后,不会使用
    bar

  • 您提到了线程,它有自己的限制和特点:

    该特性允许您跨线程授予变量的所有权

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

    举几个例子:

    • 类型
      T
      Send+Sync
      ,它可以跨线程发送并在线程之间共享
    • 类型
      T
      !发送+同步
      ,它可以跨线程共享,但不能在线程之间发送。例如,只能在原始线程上销毁的窗口句柄
    • 类型
      T
      Send+!Sync
      ,它可以跨线程发送,但不能在线程之间共享。例如,它将只能在单个线程上使用其运行时借用检查,因为它不使用原子(多线程安全组件)
    • 类型
      T
      !发送+!Sync
      ,它只能在创建它的线程上运行。例如,它不能跨线程发送自身的副本,因为它不能原子地计数引用(请注意这样做),并且由于它没有生命周期来强制在跨线程边界发送时存在自身的单个副本,因此它不能跨线程发送
  • 在我的第三个示例中,我使用
    &str
    而不是
    &String
    ,这是因为(您可能需要向下滚动才能看到它),因此在需要
    &str
    的任何地方,我都可以插入
    &String
    ,因为编译器将自动排序

    • 项目的可变性本质上是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 lifetimes: {:?}", foo);
      
      my\u string.push\u 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);
      
      如果你能对生命周期进行注释,它们看起来会像这样:

      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");
        
        因为在可变使用
        foo
        之后,不会使用
        bar

      • 您提到了线程,它有自己的限制和特点:

        该特性允许您跨线程授予变量的所有权

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

        举几个例子:

        • 类型
          T
          Send+Sync
          ,它可以跨线程发送并在线程之间共享
        • 类型
          T
          !发送+同步
          ,它可以跨线程共享,但不能在线程之间发送。例如,只能在原始线程上销毁的窗口句柄
        • 类型
          T
          Send+!Sync
          ,它可以跨线程发送,但不能在线程之间共享。例如,它将只能在单个线程上使用其运行时借用检查,因为它不使用原子(多线程安全组件)
        • 类型
          T
          !发送+!Sync
          ,它只能在创建它的线程上运行。例如