【问题标题】:Mutating variable after ownership transfer所有权转移后的变异变量
【发布时间】:2021-03-01 22:21:25
【问题描述】:

我知道如何让代码工作我只是想知道为什么会这样。

假设以下程序:

fn dummy(name: String) {
    let last_name = " Wang".to_string();
    name.push_str(&last_name);
    println!("Hello, {}", name);
}

fn main() {
    println!("What is your name?");
    let mut name = String::new();
    std::io::stdin().read_line(&mut name).expect("Couldn't read input!");
    name.pop();
    dummy(name);
}

尝试编译时出现以下错误:

error[E0596]: cannot borrow `name` as mutable, as it is not declared as mutable
 --> print.rs:3:5
  |
1 | fn dummy(name: String) {
  |          ---- help: consider changing this to be mutable: `mut name`
2 |     let last_name = " Wang".to_string();
3 |     name.push_str(&last_name);
  |     ^^^^ cannot borrow as mutable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0596`.

我知道只需在函数定义中在name 旁边添加mut 即可解决此问题,但是当变量name 之前在main 函数内部定义为可变时,为什么需要在函数定义中将其声明为可变?

编译器不应该知道变量之前是可变的吗?为什么它不能随之转移所有权和可变的“属性”?

也许这是个愚蠢的问题,但我是 Rust 的新手。如果它表现得如此,它会引入一些新问题/错误的可能性吗?如果有,你能举一些例子吗?

【问题讨论】:

  • 可变性是变量级别的,而不是类型级别的,所以前面的可变性无关紧要。 name: String 本质上的意思是“我要一个String,它是不可变的”

标签: rust transfer ownership


【解决方案1】:

name 作为参数传递的事实只是一个细节。 在这个简化的例子中,我们可以重现相同的效果。

fn main() {
    let mut name1 = "first".to_owned();
    name1.push_str(" second");
    let mut name2 = name1;
    name2.push_str(" third");
    let name3 = name2;
    // name3.push_str(" fourth"); // rejected
    let mut name4 = name3;
    name4.push_str(" fifth");
    println!("{}", name4);
}

字符串的所有权从name1 更改为name2name3 然后name4 并且这些变量(绑定)中的每一个决定(有或没有mut)是否是现在的字符串唯一的所有者,可以变异。

初始化函数的参数类似于初始化另一个变量(借用/所有权转移/复制...),一旦进入函数,参数就会被视为任何其他局部变量,在这种情况下可能是可变的或不可变的. 如果您打算修改此参数,您可以使用mut 声明它或将其转移到另一个使用mut 声明的局部变量。

请注意,我们在这里处理的是,而不是引用。 当然,您不能从&T 初始化&mut T。 但是在引用上添加mut(如mut &Tmut &mut T)提供了将此引用重新分配给另一个值的能力(被认为是可变的或不可变的,取决于正确的mut)。 如果你熟悉 C 或 C++,这类似于在声明指针时在星号之前或之后(或两边)使用const

简而言之,在变量上使用mut 是相对于您的意图在您的算法中修改存储在该变量中的内容,但它不是该变量内容的属性。

【讨论】:

    【解决方案2】:

    除了@prog-fh 的回答之外,Rust 的一个重要特性是它允许 local 推理。换句话说,当您查看定义时,您应该拥有确定代码是否正确以及它可以做什么或不可以做什么所需的所有信息。在您的情况下,这意味着如何调用 dummy 以及传递什么参数并不重要,它的定义必须能够独立存在。所以你应该能够通过查看这部分代码就知道所有需要了解的内容:

    fn dummy(name: String) {
        let last_name = " Wang".to_string();
        name.push_str(&last_name);
        println!("Hello, {}", name);
    }
    

    这使得在使用其他人编写的函数时更容易以团队形式编写代码,或者在您可能不记得最初编写一段代码时的所有细节时长期维护代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-04
      • 2021-06-23
      • 1970-01-01
      • 1970-01-01
      • 2017-01-24
      • 1970-01-01
      • 1970-01-01
      • 2014-09-07
      相关资源
      最近更新 更多