The Rust programming language offers a unique approach to memory management, combining aspects of both automatic and manual memory management systems. This is achieved through an " ownership " system with rules that the compiler checks at compile time. This article will introduce you to the concept of ownership in Rust with detailed examples.
What is Ownership?
In Rust, the ownership concept is a set of rules that applies to all values. These rules dictate that each value in Rust has the following:
- A variable called its "owner".
- Only one owner at a time.
- When the owner goes out of scope, the value will be dropped.
This system exists primarily to make memory safe, eliminating common bugs such as null pointer dereferencing, double-free errors, and dangling pointers.
The Rules of Ownership
Variable Scope
The first key concept in Rust ownership is "scope." A scope is a range within a program for which a variable is valid. Here's an example:
{
let s = "Hello, world!";
// s is valid here
} // s is no longer valid past this point
In this case, s is valid from the point at which it's declared until the closing brace of its scope.
The String Type
For a more complex example, let's use the String type:
{
let mut s = String::from("Hello, world!");
s.push_str(", nice to meet you.");
// s is valid and has changed
} // s is no longer valid past this point
This case works similarly to the previous example, but we're also able to modify s. This results from the String type stored on the heap and can have a dynamic size.
Memory and Allocation
Regarding handling the String type, Rust automatically takes care of memory allocation and deallocation. In the example above, when s goes out of scope, Rust automatically calls the drop function, which returns the memory taken s back to the operating system.
Ownership and Functions
The ownership rules apply when interacting with functions as well. When a variable is passed to a function, the ownership of that variable is moved to the function (known as a "move"). After the move, the original variable can no longer be used.
fn main() {
let s = String::from("hello"); // s comes into scope
takes_ownership(s); // s's value moves into the function
// ... and so is no longer valid here
//println!("{}", s); // this line would lead to a compile error
}
fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The memory is freed.
In the example above, the println! line after the takes_ownership function call would result in a compile error because the ownership of s was moved to the takes_ownership function.
Borrowing and References
To allow access to data without taking ownership, Rust uses a concept called "borrowing." Instead of passing objects directly, we pass references to them.
fn main() {
let s = String::from("hello"); // s comes into scope
does_not_take_ownership(&s); // s's value is referenced here
// s is still valid here
println!("{}", s); // this line will compile and print "hello"
}
fn does_not_take_ownership(some_string: &String) { // some_string is a reference
println!("{}", some_string);
} // Here, some_string goes out of scope. But because it does not have ownership, nothing happens.
In this case, &s creates a reference to the value of s but does not own it. Because it does not have ownership, the value it points to will not be dropped when the reference goes out of scope.


