- immutable & mutable = read-only & read-write = read & write
Just like file properties.
Better names than "mutable references" (&mut
) and "immutable references" (&
) would have been "exclusive references" and "shared references". Shared references can have interior mutability, and sometimes the important part about &mut
-taking interfaces is the exclusivity guarantee. I see you do cite Matt's "a unique perspective" article later.
a) The owner is created immutable by default, so the object will be unchangable, read-only.
let owner_read_only = vec![0, 5, 15];
b) If it is created mutable, the object will be changable, write.
let mut owner_write = vec![0, 5, 15];
owner_write.push(13);
mut
or not in bindings (like let
, or function parameters) is pretty much a lint. In particular, it is not part of the type, and not something you cannot change. The owned value -- the Vec
in this case -- is not intrinsically different based on being bound to let mut
or not. If you have a non-mut
binding, you just can't mutate through that particular binding. But you can always just create a new binding to what you own. So this all works:
fn modify<T>(mut v: Vec<T>) -> Vec<T> {
v.clear();
v
}
fn main() {
let read_only = vec![1, 2, 3];
let mut read_only = read_only;
read_only.clear();
println!("Hmm... {read_only:?}");
let read_only = vec![1, 2, 3];
let still_read_only = modify(read_only);
println!("Hmm... {still_read_only:?}");
}
This confusion between types &mut T
and bindings let mut name = ...
seems to persist later on:
// provides read-only access
let access_read_only = &owner_read_only;
// provides write access
let mut access_write = & mut owner_write;
access_write.push(3);
You need the mut on both sides: On the left, you define a named write access; On the right, the owner gives an unnamed write access. The = binds them together.
You don't need access_write
to be a mutable binding, unless you're going to assign some different value to it later. This works:
fn modify(_: &mut [i32]) {}
fn main() {
let mut v = vec![];
let non_mutable_binding_to_mutable_reference = &mut v;
non_mutable_binding_to_mutable_reference.push(0);
modify(non_mutable_binding_to_mutable_reference);
}
And in fact, if you make the latter binding let mut
, the compiler will warn you that you don't need it... provided you have not turned off warnings
An owner can never access its object directly, even though it may sometimes seem so. In the last line above, the push method is given write access for the duration of its call.
I'm not sure what that means either. This is fine and involves no references.
#[derive(Default)]
struct S {
a: String,
b: String,
}
fn main() {
let mut s = S::default();
s.a = String::new();
s.b = String::new();
}
Or even simpler,
fn main() {
let mut i = 0;
i = 1;
i = 2;
}
Every example above has its own scope through the use of {...} and all accecces created within will end when the bracket is closed again.
Borrow lifetimes don't have to be limited to the blocks where you create or assign them. In your example, the variables are defined in each block and aren't returned out of it. And the borrows you create aren't used or otherwise bound to be longer than the variables. Therefore the borrows will be limited to the blocks in your example... but this is not true of all borrows.
Nor do borrows have to extend to the end of the block they're created in. Rust use to work this way (before "NLL" -- non-lexical lifetimes), but that's no longer the case and hasn't been for years now. If you no longer use a borrow, the borrow may end before the variable that holds the borrow goes out of scope. (Sometimes it does seem borrow must extend to the end of a scope when you hold it in a type that has a destructor, as running the destructor usually counts as using the borrow.)
Funny enough, the last two do compile. But as soon as you would use the wrong access, you will get an error.
And therefore, if you never use the first borrows in your example, they will effectively end immediately, so there's no conflict in creating the &mut owner_write
borrow.
Or as an alternative way to look at it, creating the &mut owner_write
effectively ended all preceding borrows (due to the exclusivity guarantee), and thus compiles so long as you don't try to use the borrows that have ended. Using the owned value itself would have a similar effect.
- Object Orientation: Only use methods to create access to an object. Every getter method returns a copy of the data it retrieves.
I would say:
- Don't use getters and setters within the module that defines an object; just access fields directly.
- If you have a
&mut SomeType
getter to a SomeType
field, consider just making that field public.
- If you have a getter and setter for some field and you're not upholding some variant in the setter, similarly consider just making that field public.
- If you have a
&SomeCloneableType
getter causing you borrow problems, an alternative to changing the getter to return a clone is to just clone at the call site.
let value = obj.getter().clone();
Here's a related article you may find illuminating.