This is supplementary/separate from the Twitch Streams (see sidebar for links), intended for discussion here on lemmy.
The idea being, now that both twitch streams have read Chapter 4, we can have a discussion here and those from the twitch streams can have a retrospective or re-cap on the topic.
This will be a regular occurrence for each discrete set of topics coming out of The Book as the twitch streams cover them
Ownership and the borrow checker are obviously a fundamental and unique topic to rust, so it’s well worth getting a good grounding in AFAICT.
- Anyone up to trying to summarise or explain ownership/borrow-checker in rust?
- it can be a good exercise to test your understanding and get feedback/clarification from others … as well as probably a good way to teach others
- Any persistent gripes, difficulties or confusions?
- Any of the quizzes from The Book stump you?
- Any hard learnt lessons? Or tried and true tips?
If I had to explain ownership in rust (based on The Book, Ch 4)
I had a crack at this and found myself writing for a while. I thought I’d pitch at a basic level and try to provide a sort core and essential conceptual basis (something which I think The Book might be lacking a little??)
Dunno if this will be useful or interesting to anyone, but I found it useful to write. If anyone does find any significant errors, please let me know!
General Idea or Purpose
Ownership
// > ON THE HEAP // Ownership will be "moved" into this function's scope fn take_ownership_heap(_: Vec<i32>) {} let a = vec![1, 2, 3]; take_ownership_heap(a); // ERROR let b = a[0]; // CAN'T DO: value of `a` is borrowed/used after move // `a` is now "dead", it died in `take_ownership_heap()`;
my_variable.copy()
) and in the case of custom types (egstruct
s) added to or implemented for that particular type (which isn’t necessarily difficult).// > ON THE STACK // An integer will copied into `_`, and no ownership will be moved fn take_ownership_stack(_: i32) {} let x = 11; take_ownership_stack(x); let y = x * 10; // NOT A PROBLEM, as x was copied into take_ownerhsip_stack
Borrowing (with references)
fn borrow_heap(_: &Vec<i32>) {} let e = vec![1, 2, 3]; // pass in a reference borrow_heap(&e); let f = e[0]; // NOT A PROBLEM, as the data survived `borrow_heap` // because `e` retained ownership. // &e, a reference, only "borrowed" the data
shared references
are “read only” references, whileunique references
are mutable references that enable their underlying data to be mutated (AKA, mutable references).mutable or unique references
andreference variables that are mutable
. Aunique reference
is able to mutate the data pointed to. While amutable variable that is also a reference
can have its pointer and the data/memory and points to mutated. These are independent aspects and can be freely combined.reference
is just another variable whose data is a pointer or memory address.// >>> Can have multiple "shared references" let e_ref1 = &e; let e_ref2 = &e; let e1 = e_ref1[0]; let e2 = e_ref2[0]; // >>> CANNOT have shared and mutable/unique references let mut j = vec![1, 2, 3]; // A single mutable or "unique" reference let j_mut_ref = &mut j; // can mutate the actual vector j_mut_ref[0] = 11; // ERROR let j_ref = &j; // CANNOT now have another shared/read-only reference while also having a mutable one (j_mut_ref) // mutation actually needs to occur after the shared reference is created // in order for rust to care, otherwise it can recognise that the mutable // reference is no longer used and so doesn't matter any more j_mut_ref[1] = 22; // same as above but for stack data let mut j_int = 11; let j_int_mut_ref = &mut j_int; // ERROR let j_int_ref = &j_int; // CANNOT assign another reference as mutable reference already exists *j_int_mut_ref = 22; // dereference to mutate here and force rust to think the mutable reference is still "alive"
Ownership and read/write permissions are altered when references are created
unique reference
.// >>> References remove ownership permissions fn take_ownership_heap(_: Vec<i32>) {} let k = vec![1, 2, 3]; let k_ref = &k; // ERROR take_ownership_heap(k); // Cannot move out of `k` into `take_ownership_heap()` as it is currently borrowed let k1 = k_ref[0]; // if the shared reference weren't used here, rust be happy... // as the reference's lifetime would be considered over // >>> Mutable reference remove read permissions let mut m = 13; let m_mut_ref = &mut m; // ERROR let n = m * 10; // CANNOT read or use `m` as it's mutably borrowed *m_mut_ref += 1; // again, must use the mutable reference here to "keep it alive"
Lifetimes are coming
fn first_or(strings: &Vec<String>, default: &String) -> &String { if strings.len() > 0 { &strings[0] } else { default } } // Does not compile error[E0106]: missing lifetime specifier --> test.rs:1:57 | 1 | fn first_or(strings: &Vec<String>, default: &String) -> &String { | ------------ ------- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `strings` or `default`
'a
s in the following code:fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
.first_or
takes two references but returns only one reference that will, depending entirely on runtime logic, depend on one of the two input references. IE, depending on what happens at runtime, one of the input references have a longer lifetime than the other. As Rust cannot be sure of the lifetimes of all three references, the programmer has to provide that information. A topic for later.