Tags: #rust #rustlang #ownership #memory
NOTE: all information learned from https://doc.rust-lang.org/stable/book/ch04-00-understanding-ownership.html
Understanding ownership requires understanding ‘stack’ vs ‘heap’ memory.
drop
function which Rust will call when out of scope (to explicitly deallocate heap memory).a = 1; b = a
) because they exist in stack memory and are known size (i.e. cheap to copy).
Copy
trait that enable this.a = String::from("hello"); b = a
).
Copy
trait (which is a common error).a = String::from("hello"); b = a.clone()
) which will actually duplicate the heap memory (so it’s not cheap!).drop
is not called, even if the owner was created within the function, as would normally be the case if a variable went out of scope at the end of the function.NOTE: all information learned from https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html
Taking ownership and then returning ownership with every function is a bit tedious. To prevent this you can pass a ‘reference’ to a complex type (e.g. function foo(s: &String)
and caller foo(&a)
).
In the above example the s
variable will (depending on function implementation) go out of scope, and yet nothing will happen (i.e. it won’t be dropped) because the function doesn’t own what s
refers to.
In order to mutate something borrowed, the caller and the receiver need to define the type as a ‘mutable’ type:
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
{let a = &mut s;} let b = &mut s;
(as a
will go out of scope before b
is reached).NOTE: all information learned from https://doc.rust-lang.org/stable/book/ch10-03-lifetime-syntax.html
Lifetimes ultimately are coupled to references, hence the compiler uses what’s called a “borrow checker” to validate lifetimes (as a ‘reference’ is a term related to the concept of “borrowing”).
Rust prevents variables from trying to hold references to data that has since gone out of scope (i.e. dangling pointer).
The ‘lifetime’ of a reference begins when the reference is created and ends when it’s last used.
If a function returns a reference that changes depending on some logic (e.g. if X return A else return B, where A/B are two different references) then the borrow checker can’t statically analyse if your code is safe as it doesn’t know which reference will be returned at runtime.
In those cases we need to add lifetime annotations…
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
The longest
function definition states all references in the signature must have the same lifetime 'a
.
We’re specifying that the borrow checker should reject any values that don’t adhere to these constraints.
The lifetime named ‘static’ is a special lifetime. It signals that something has the lifetime of the entire program.
String literals can be assigned the type &'static
lifetime annotation as a way to indicate the reference is always alive, i.e. they are baked into the data segment of the final binary.