In C/C++, the semantic about pointer and const is very clear:
int i = 10;
const int * const p = *i;
but in Rust, I am still confused about the semantic of reference and mut. For example,
let b = &mut Box::new(5);
let mut b = Box::new(5);
let mut b = &mut Box::new(5);
// No compile error.
fn main() {
let mut b = Box::new(5);
*b = 9;
println!("b = {}", b);
}
fn main() {
let b = &mut Box::new(5);
*b = 9; // error[E0308]: mismatched types
println!("b = {}", b);
}
The rules of writing types and expressions in Rust are much simpler than in C++. I don't really know what exactly confuses you in the code you provided. It would help if you asked more specific questions.
Regarding mut, it's important to know that it has two distinct, not really connected meanings. First, it's a part of the exclusive reference syntax: &mut target (as opposed to shared reference syntax &target). This can be used both in types (e.g. &mut Box<u32> type is an exclusive reference to Box<u32>) and in expressions (e.g. &mut Box::new(5) expression is an exclusive reference to Box::new(5)). Forgetting to take a reference to something usually results in type errors, so the compiler messages should help you with that.
The second meaning of mut is that it marks a binding as mutable. For example, it can appear in this role in a local variable declaration:
let mut b = 5;
Or in a pattern:
if let Some(mut x) = something { /*...*/ }
Or in an argument declaration:
fn function_name(mut arg_name: u32) { /*...*/ }
Please note that in the second meaning, mut has no impact on type of the binding or values of any expressions. It only allows you to create a mutable reference to the value of the binding, which is not allowed otherwise. So you may or may not need to declare a variable with mut, depending on how you use the variable later. To make it easy, you can just make a binding mut when the compiler asks you to, and remove mut when the compiler complains about it (note that both of these errors/warnings are not type errors).
The main difference in this example is the use of field access (c.number). Box acts as a smart pointer; along with other things, Box implements the DerefMut trait. This is what allows *b = 9 to function -- a &mut isize is coerced and dereferenced to allow the assignment.
int *i;
const int *i;
int const *i;
int *const i;
int const *const i;
Attention check: two of those lines mean the same thing! Which ones?
In C const is a qualifier that can be applied to any type. But the way constness works is different between pointer types and non-pointer types. constness doesn't follow non-pointers the way it does pointers; you can pass a const int to a function expecting int, but you can't pass a const int * to a function expecting int * (not without a cast, anyway). It's almost like const has two meanings: one for const-qualified pointers, and one for named variables...
... Which is of course exactly how mut works in Rust, as others in the thread have already explained.
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let &mut b = 5;
| ^^^^^^ expected integer, found `&mut _`
|
= note: expected type `{integer}`
found mutable reference `&mut _`
So, in this case Rust expects the right side to be some king of exclusive reference. The left side in this case is called a pattern: it describes the shape of the thing we want to use, and binds the variables to the values inside. For example, this will work:
fn main() {
let &mut b = &mut 5;
println!("{}", b);
}
In this case, we're saying the following: "we are putting here an exclusive reference to something, please bind the value behind this reference to variable b". So, b is bound to the value 5.
fn main() {
let a = 3;
let mut b = &a;
*b = 9; // error[E0594]: cannot assign to `*b` which is behind a `&` reference
}
Here, b is assigned an immutable reference as value. An immutable reference allows no mutation through it, so you cannot mutate a through b. The mut on b is for something else — it allows you to change where the pointer points to, but not the contents behind the pointer.
fn main() {
let a = 3;
let b = 4;
let mut c = &a;
println!("{}", c); // prints 3
c = &b;
println!("{}", c); // prints 4
}
To add to that, the non-transitive property of const in C makes all sorts of constructs invalid that """should be""" valid intuitively. For instance, there's the famous problem whereby you can't pass your char **argv to a function expecting const char **.