Search code examples
rusttransferownership

Mutating variable after ownership transfer


I know how to make code work I'm just wondering why is it so.

Suppose the following program:

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);
}

When trying to compile it the following error occurs:

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`.

I know that just adding mut next to name in function definition solves this problem, but why need for declaring it as mutable in function definition when variable name was defined as mutable previously inside of main function?

Shouldn't the compiler know that variable was mutable before and why can't it transfer ownership and mutable "property" along with it?

Maybe this is stupid question but I'm new to Rust. If it behaved so would it introduce possibility for some new problems/bugs? If yes, can you give some examples?


Solution

  • The fact that name is passed as a parameter is just a detail. In this simplified example, we can reproduce the same effect.

    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);
    }
    

    Ownership of the string changes from name1 to name2, name3 then name4 and each of these variables (bindings) decide (with or without mut) if the string, of which it is now the sole owner, can be mutated.

    Initialising the parameter of a function is similar to initialising another variable (borrowing/ownership-transfer/copy...) and once inside the function the parameter is seen as any other local variable which may be mutable or not in this context. If you intend to modify this parameter you can either declare it with mut or transfer it to another local variable declared with mut.

    Note that we are dealing with values here, not references. Of course, you cannot initialise a &mut T from a &T. But prepending mut on a reference (as in mut &T or mut &mut T) offers the ability to reassign this reference towards another value (considered mutable or not, depending on the right mut). If you are familiar with C or C++, this is similar to using const before or after the star (or both sides of) when declaring a pointer.

    In short, using mut on a variable is relative to your intention to modify what is stored in this variable in your algorithm, but it is not a property of the content of this variable.