Reputation: 3609
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
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
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