# Ownership
At one point in time, only one variable may own a piece of data. If a given variable holds data on a stack, the data is just copied:
let a = 4;
let b = a;
// there will be two "4" on the stack
In case of heap data, only the pointer is copied:
let a = String::from("abc");
let b = a;
// a is no longer a valid variable! b is the owner now!
Unique to Rust, the a
variable becomes invalid when the code above gets
executed. If we try to access it, it will result in a panic. Thanks to it,
during runtime Rust will not try to drop
(free
) this memory space twice,
which would be wrong.
It is called a move. a
gets moved to b
.
Passing a value to a function also causes a move!
fn main() {
let s = String::from("abc");
some_func(s);
// s is no longer valid. The ownership was moved!
}
fn some_func(data: String) {
// At the end, `data` goes out of scope and `drop` is called
}
A function may also move ownership by returning a value. Then, the calling function's scope owns the variable.
# References
Sometimes we want to pass a value to a function, but also to continue to use it in the calling function. We could make the called function return the passed-in value, but that would be a weird workaround, especially if that function is supposed to return some other data as well. References come to the rescue.
fn main() {
let s1 = String::from("hello");
// pass as a reference
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
// we can still use `s1`
}
fn calculate_length(s: &String) -> usize {
s.len()
// s has no ownership. When it goes out of scope, nothing happens
}
Creating a reference is called borrowing. References are immutable by default. We can change that:
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Both the s
variable and the some_string
reference need to use the mut
keyword to allow mutations of the value.
WARNING
At any given time, you can have either:
- one mutable reference
- any number of immutable references.
This will fail:
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // Wrong!
println!("{}, {}", r1, r2);
Also, if there is an immutable reference, another one that is mutable cannot be created:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
That's because if one function would get an immutable reference, it will expect that the value should not change suddenly.
TIP
A reference’s scope starts from where it is introduced and continues through the last time that reference is used. This is OK:
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // It's OK!
println!("{}", r2);
r1
gets created, but it is never used afterwards. r2
may be created.
With Rust, it's impossible to have dangling pointers. Rust will complain of such issues at compile time.
# Dereferencing
let x = 5;
let y = &y;
assert_eq!(5, x);
assert_eq!(5, y); // WRONG! i32 and &i32 are different types
Sometimes we might need to obtain an actual value that the reference is pointing to. We can do that we the dereferencing operator:
let x = 5;
let y = &x;
assert_eq!(5, *y); // OK
Smart Pointers
Smart pointers might be dereferenced as well.
# Slices
Slice is a part of some collection. It is basically a pointer to some index of that collection and a count of elements starting from that index.
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
# String Slice
Sometimes we need to "extract" a part of a string. Instead of creating a totally new string, disconnected from the original one, we can use a String Slice.
let s = String::from("abcd");
let slice = &s[1..3];
slice
contains the pointer to the first character of the slice, and the length
of the slice. Additionally, the slice has a reference to s
(?), so s
cannot
be mutated while slice
is still in use.
TIP
All string literals are slices!
let s = "abc"; // It's a slice!
A reference to a string is treated as a string slice.
← Basics Structures →