Reputation: 11
I have a question related to move semantics in Rust. In my understanding, "mut" keyword in Rust is to make some variables mutable, i.e., mutable variables can be bound to another values again; however this mutability is just for bindings. So, if I really want to change a value of the variable, then I should use "&mut" keyword like this:
let mut two = 2;
let t = &mut two;
*t += 1;// update the value of two, not only bind t to another values
print!("{}", t); // here, t is 3
However, in a case with using structs, it does's not seems like this.
Here, this is an example code (https://doc.rust-lang.org/book/ch05-01-defining-structs.html):
let mut user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("[email protected]");
Why can I rewrite the field "email" of user1? It does not seem like a re-binding of user1.
Upvotes: 1
Views: 2042
Reputation: 37
No, you don't need to mutably borrow a variable in order to change it.
let mut variable: i32 = 2;
variable += 1;
will work just fine. Mutable borrow can be useful when you want your function to modify the struct that it is receiving. If a function foo takes the ownership of the struct that it receives as an argument instead of receiving a mutable reference, then the lifetime of that struct ends when function foo returns. You often don't want this and that's why you pass a mutable reference to a function. Mutable references are return types of some functions. For instance, get function of HashMap returns an optional of a mutable reference. It is an optional of a mutable reference, not of an own type because the you want HashMap to continue to own the data and you also want to allow the caller of get function to be able to mutate the data that HashMap owns. Let's say HashMap function get returned an optional of an owned type by copying its data. In that case, any mutation that the caller would do would have no impact on the data stored in the HashMap, because the caller has received a copy of the data.
Upvotes: 0
Reputation: 13620
I think you are confused about how variables work. Variables are like cups that can store data, for strongly typed languages like rust they can hold only one type of data.
So when you declare a value basically you are instructing the computer to allocate some space in the memory:
let x: u32;
In above example, basically you are saying "computer, give me a cup big enough to hold u32 data (32 bits)".
Then computer gives you that cup, in our case x is the owner. x
is how you hold onto that cup, in other words owner of the memory space.
Now lets fill that cup with a proper data:
x = 42;
Once mighty computer reserves that cup for x, it belongs to x until x relinquishes the ownership:
{
let x: u32 = 42;
let y = x;
}
{
let x: u32 = 42;
drop(x);
}
{
let x: u32 = 42;
}
// x is dropped here
Once memory space is released, computer can give it to other variables.
You can check the address of the memory space as follows:
println!("address of var: {:p}", &x);
Computer gives you that memory space when you initialize x. This is Resource Acquisition Is Initialization (RAII) at work:
fn main() {
let x: u32;
println!("address of x: {:p}", &x);
x = 12;
println!("{}", x);
}
You get compiler error:
12 | println!("address of x: {:p}", &x);
| ^^ use of possibly uninitialized `x`
But if you check the adress after the initialization, it compiles without any error:
fn main() {
let x: u32;
x = 12;
println!("address of x: {:p}", &x); // address of x: 0x7ffc8183402c
}
When declaring a variable you have a contract with the compiler. mut
is one of the terms of that contract.
By not using mut
keyword, you are simply saying I will never change what is in the cup (in memory space x points) once I fill it (initialize a variable). Once you fill your cup, it stays that way.
But if you use mut
keyword, your contract says you can put any value in that memory space, as long as it is correct type.
In both cases owner of that memory space is x. Mutability has nothing to do with ownership.
Now, about your example:
let mut two = 2;
let t = &mut two;
*t += 1;
In second line, you are taking a mutable reference to variable two
. In other words t borrows what x points to. In the next line, you are filling the cup with 3. Since you are using mutable reference, you had to dereference in the following line *t += 1;
.
let mut two = 2;
println!("address of &two: {:p}", &two);
let t = &mut two;
*t += 1;
println!("address of t: {:p}", t);
This prints:
address of &two: 0x7ffc5869c9c4
address of t : 0x7ffc5869c9c4
two
is the owner, t just borrows:
let mut two = 2;
{
let t = &mut two;
*t += 1;
println!("{:?}", t);
}
two += 10;
println!("{:?}", two);
Again mutability has nothing to do with ownership, Rust puts constrains on borrowing mutable values to eliminate confusion since it is easy to loose control of who does what when multiple variable can change what is in your cup.
In case of User type, if you initialize user1
value as mutable, you can change what you store in its properties. It is just like cup holding other cups, or pointer to other cups stored in the heap.
Upvotes: 0
Reputation: 19662
I think your issue stems from a small confusion around mut
as a keyword, and an analogy might help.
Imagine that I own a car. Actually, let's first define what a car is:
pub struct Car {
fuel: usize,
pub color: String,
pub wheel_count: u8
}
Let's define my car
let mut my_car:Car = Car { fuel: 100, color: "Green".to_string(), wheel_count: 4 };
This is my car. I've defined it as a changeable entity (let mut
), so if I want to, I can fuel it up and turn it blue
my_car.fuel += 20;
my_car.color = "Blue".to_string();
This is down to the definition of the variable itself. If I had just let
my car, I wouldn't have been able to do this. Assigning a variable with let mut
indicates that it can be modified, across all fields, by whoever has either:
I then decide to go to the fuel station to fuel it up. I lend my car to the attendant so they can do it for me:
pub fn lend_car_to_attendant(target_car: &mut Car) {
target_car.fuel += 20;
}
He could, if he had wanted, have driven to the paint shop and changed the color of the car, as the entire car is temporarily his. It is also worth noting that while he has the car, I cannot do anything with it. I lent it to him, and until he returns the borrow
, it is his.
Now, of course, outside of the time he has the car, anybody can peek at my car and admire its color (or the number of wheels it has). All public attributes of my_car
are publicly inspectable by anybody with an immutable borrow.
I then decide to try another fuel station, and it turns out to be pretty dishonest:
pub fn lend_car_to_naughty_attendant(target_car: &mut Car) {
target_car.fuel += 20;
lend_car_to_paint_shop(target_car);
}
pub fn lend_car_to_paint_shop(target_car: &mut Car) {
target_car.color = "Bubblegum Pink".to_string();
}
My car came back bubblegum pink!
We could have avoided this by having somebody watch over the car. If we give somebody an immutable borrow to &my_car
, and then try to go to that terrible fuel station again, the program will not compile at all (example here)
Upvotes: 4