Pepernoot
Pepernoot

Reputation: 3609

How can Rust know if a variable is mutable?

The following C# code compiles fine:

static readonly List<int> list = new List<int>();
static void Main(string[] args)
{
    list.Add(1);
    list.Add(2);
    list.Add(3);
}

If I write similar code in Rust, it won't compile because it cannot borrow immutable v as mutable:

let v = Vec::new();
v.push(1);
v.push(2);
v.push(3);

How does the push function know v is immutable?

Upvotes: 3

Views: 739

Answers (2)

Peter Hall
Peter Hall

Reputation: 58695

In Rust, bindings are immutable by default. So you might think that the following are equivalent:

readonly List<int> list = new List<int>(); // C#
let list = Vec::new();                     // Rust

And these:

List<int> list = new List<int>(); // C#
let mut list = Vec::new();        // Rust

But, as you found out, this isn't quite the case.

Inside the Add method in the C# version, there is no information about the binding that you used to invoke it. The Add method isn't able to declare that it mutates its data so there is no way for the C# compiler to prevent you passing a reference to a readonly binding. The readonly keyword prevents you overwriting the list binding with a completely new List, but it doesn't prevent you changing data inside the one you have. C# prevents you from changing the value of a readonly binding, but the value in this case is a pointer to your data, not the data itself.

In Rust, if a method needs to mutate the underlying data, it must declare its first argument to be self or &mut self.

In the case of self, then the data is moved into the method, and you can no longer use the original binding. It doesn't matter if the method changes the data because the caller can't use that binding any more.

In the case of a mutable reference, &mut self, Rust will only let you create it if the original binding is also mutable. If the original binding is immutable then this will produce a compile error. It is impossible to call v.push if v is immutable, because push expects &mut self.

This can be restrictive, so Rust provides tools that let you fine-tune this behaviour to encode exactly the safety guarantees that you need. If you want to get something close to the C# behaviour, you can use a RefCell wrapper (or one of the several other wrapper types). A RefCell<Vec<T>> doesn't itself have to be mutable for functions to be able to unwrap it and modify the Vec inside.

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 430310

All variables are immutable by default. You must explicitly tell the compiler which variables are mutable though the mut keyword:

let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);

Vec::push is defined to require a mutable reference to the vector (&mut self):

fn push(&mut self, value: T)

This uses method syntax, but is conceptually the same as:

fn push(&mut Vec<T>, value: T)

I highly recommend that you read The Rust Programming Language, second edition. It covers this beginner question as well as many other beginner questions you will have.

Upvotes: 9

Related Questions