Understanding Ownership
Understanding Ownership
Ownership is Rust's most unique feature and has deep implications for the rest of the
language. It enables Rust to make memory safety guarantees without needing a garbage
collector.
What is Ownership?
Ownership is a set of rules that govern how a Rust program manages memory. All programs
have to manage the way they use a computer's memory while running.Unlike other
languages Rust uses a different approach, it manages memory through a system of
ownership with a set of rules that the complier checks. If any of the rules are violated, the
program won't compile.
Ownership Rules
• Each value in Rust has an owner.
• There can only be one owner at a time.
• When the owner goes out of scope, the value is dropped.
Variable Scope
A scope is the range within a program for which an item is valid. For example
let s = "hello";
The variable s refers to a string literal, where the value of the string is hardcoded into the text
of our program. The variable is valid from the point at which it's declared until the end of for
the current scope.
let s = String::from("hello");
The double colon :: operatory allows us to namespace this particular from function under the
String type.This kind of string can be mutated:
Memory allocation
In the case of a string literal, we know the contents at compile time, so the text is hardcoded
directly into the final executable.With the String type, in order to support a mutable,
growable piece of text, we need to allocate an amount of memory one the heap, unknown
at compile time, to hold the content.This means:
Rust takes a different path: the memory is automatically returned once the variable that owns
it goes out of scope. Rust calls a special function for us when variable goes out of scope. This
function is called drop, and it's where the author of String can put the code to return the
memory. Rust calls drop automatically at the closing curly bracket.
Variable and Data Interreacting with Move
Multiple variables can interact with the same data in different ways in Rust.
let x = 5;
let y = x;
We can probably guess what this is doing: "bind the value 5 to x; then make a copy of the
value in x and bind it to y." This is indeed what is happening, because integers are simple
values with a known, fixed size, and these two 5 values are pushed onto the stack.
Now let's look at the String version:
let s1 = String::from("hello");
let s2 = s1;
This looks very similar, so might assume that the way it works would be same, But this isn't
quite what happens.
A String is made up of three parts, a pointer to the memory that holds the contents of the
string, a length, and a capacity. This group of data is stored on the stack. Content is stored on
heap memory.When we assign s1 to s2, the String data is copied, meaning we copy the
pointer, the length, and the capacity that are on the stack. We do not copy the data on the
heap that the pointer refers to.
Earlier, we said that when a variable goes out of scope, Rust automatically calls the drop
function and cleans up the heap memory for that variable. when s2 and s1 go out of scope,
they will both try to free the same memory. This is known as a double free error. Freeing
memory twice can lead to memory corruption, which can potentially lead to security
vulnerabilities.
The concept of copying the pointer, length and capacity without copying the data probably
sounds like making a shallow copy, But because Rust also invalidates the first variable, instead
of being called a shallow copy, it's known as a move.That solves our problem!
let s1 = String::from("hello");
let s2 = s1.clone();
let x = 5;
let y = x;
But this code seems to contradict what we just learned. The reason is that types such as
integers that have a known size at compile time are stored entirely on the stack, so copies of
the actual values are quick to make.
Rust has a special annotation called the Copy trait that we can place on types that are stored
on the stack, as integers are. If a type implements the copy trait, variables that use it do not
move, but rather are trivially copied, making them still valid after assignment to another
variable.
Here are some of the types that implement Copy:
• All the integers types
• The Boolean type
• All the floating-point types
• The character type, char.
• Tuples, if they only contains type that also implement Copy.
Ownership and Functions
The mechanics of passing a value to a function are similar to those when assigning a value to
a variable. Here is an example with some annotations showing where variables go into and
out of scope.
Return values and Scope
Returning values can also transfer ownership.
fn main() {
let s1 = gives_ownership();
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
}
The ownership of a variable follows the same pattern every time.While this works, taking
ownership and then returning ownership with every function is bit tedious. What if we want
to let a function use a value but not take ownership?
Rust has a feature for using a value without transferring ownership, called references.
fn main(){
let s1 = String::from("hello");
let len = calculate_length(&s1);
The &s1 syntax lets us create a reference that refers to the value of s1 but does not own it.
Because it does not own it, the value it points to will not be dropped when the reference
stops being used.Likewise, the signature of the function uses & to indicate that the type of
the parameter s is reference.We call the action of creating reference borrowing.Just as
variable are immutable by default. so are references.
Mutable References
We can modify a borrowed value using mutable references.
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
Mutable references have one big restriction: if you a mutable reference to a value, you can
have no other references to that value.
Dangling References
In Rust, the complier guarantees that references will never be dangling references: If you
have a reference to some data, the complier will ensure that the data will not go out of
scope before the reference to the data does.
The Rule of References
• At any given time, you can have either one mutable reference or any number of
immutable references.
• References must always be valid.
With Rust's .. range syntax, if we want to start at index 0, we can drop the value before the
two periods.
let s = String::from("hello");
By the same token, if our slice includes the last byte of the String, we can drop the trailing
number.
let s = String::from("hello");
we can also drop both values to take a slice of the entire string.
With all this information in mind, let's write first_word to return a slice. The type that signifies
"string slice" is written as &str:
&s[..]
}