Help understanding borrowing on function parameters

#1

Hi!

I’m going through the rust book (read up to and including 4.2) and playing along and I have a doubt about mutability and borrowing.

I was playing with this example:

Code on playground

fn main() {
    let x = String::from("hi");
    let x = add_suffix(x);
    println!("{}", x);
}

fn add_suffix(mut s: String) -> String {
    s.push('!');
    s
}

And I was expecting it to fail, given that I declared x first as immutable, but add_suffix requires its parameter to be mut. Instead, it compiles and runs just fine.

I’m a bit confused now, as my understanding seems to check out when using references. Why does this work fine and not error on the difference of mutable/immutable variable?


Some of my exploration with references for the record:

For example, if we make add_suffix operate on a reference:

fn main() {
    let x = String::from("hi");
    let x = add_suffix(&x);
    println!("{}", x);
}

fn add_suffix(s: &String) -> &String {
    s.push('!');
    s
}

We do get a mutability compile error:

error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
7 | fn add_suffix(s: &String) -> &String {
  |                  ------- help: consider changing this to be a mutable refer
ence: `&mut std::string::String`
8 |     s.push('!');
  |     ^ `s` is a `&` reference, so the data it refers to cannot be borrowed a
s mutable

And if we make the parameter &mut then we get an error at the call site asking us to provide a mutable reference:

fn main() {
    let x = String::from("hi");
    let x = add_suffix(&x);
    println!("{}", x);
}

fn add_suffix(s: &mut String) -> &String {
    s.push('!');
    s
}
error[E0308]: mismatched types
 --> src/main.rs:3:24
  |
3 |     let x = add_suffix(&x);
  |                        ^^ types differ in mutability
  |
  = note: expected type `&mut std::string::String`
             found type `&std::string::String`

Which guides you to make that &mut:

fn main() {
    let x = String::from("hi");
    let x = add_suffix(&mut x);
    println!("{}", x);
}

fn add_suffix(s: &mut String) -> &String {
    s.push('!');
    s
}
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
 --> src/main.rs:3:24
  |
2 |     let x = String::from("hi");
  |         - help: consider changing this to be mutable: `mut x`
3 |     let x = add_suffix(&mut x);
  |                        ^^^^^^ cannot borrow as mutable

and in the end make the x binding mut too:

fn main() {
    let mut x = String::from("hi");
    let x = add_suffix(&mut x);
    println!("{}", x);
}

fn add_suffix(s: &mut String) -> &String {
    s.push('!');
    s
}

So, what am I misunderstanding regarding values and mut? Is this a bug?

And another related question, why do we write

  • fn add_suffix(s: &mut String) -> &String
    • but when not using references we have to write
  • fn add_suffix(mut s: String) -> String

Thank you!

#2

Mutability of a value is decided by the function that contains the top-level stack variable. In your first example, you transfer the value into add_suffix, so it gets to decide whether s is mutable or not; main no longer has any say in the matter.

In your second, you give a borrow of x to add_suffix, so main still decides whether x is mutable or not.

You might say “I’m never going to try to restore this old painting I own!”, but the moment you transfer ownership of it to someone else, you really have no say on et mater any more.

Because that’s the difference between an immutable binding s to a mutable borrow of a String, and a mutable binding s to a String.

1 Like
#3

:smile: thanks for the explanation, makes sense since main is giving ownership away. I hadn’t thought of it that way!

Thanks!

#4

Another way to look at it is that immutable data doesn’t exist in Rust. Mutability depends only on the way you access things.

1 Like