Rust Ownership

What Is Ownership in Rust?

Ownership is Rust’s system for managing memory. It revolves around three key principles:

  1. Each value in Rust has a single owner.
  2. When the owner goes out of scope, the value is dropped.
  3. There can only be one owner at a time. Ownership can be transferred (moved), but not copied by default.

Why Ownership Matters

Ownership helps Rust manage memory at compile time. It eliminates runtime errors like null pointer dereferencing and ensures efficient memory usage. Ownership also replaces the need for garbage collection, making Rust performant and safe.

Scope and Ownership

Ownership in Rust works within the context of scopes. A variable is considered “in scope” when it is valid and “out of scope” when it is no longer accessible.

Example 1: Ownership and Scope

fn main() {
{
let s = String::from("Hello, Rust!"); // s is in scope
println!("{}", s);
} // s goes out of scope and is dropped here
}

In the above example:

  • The String s is created inside a block.
  • When the block ends, s goes out of scope, and Rust automatically cleans up its memory.

Move in Ownership

In Rust, assigning one variable to another moves ownership. The original variable loses its ownership, making it invalid.

Example 2: Moving Ownership

fn main() {
let s1 = String::from("Rust");
let s2 = s1; // Ownership of s1 is moved to s2
// println!("{}", s1); // Error: s1 is no longer valid
println!("{}", s2);
}

Explanation:

  • Ownership of the String s1 is moved to s2.
  • Trying to use s1 after the move results in a compile-time error.

Clone: Deep Copying Data

If you need to duplicate a value without transferring ownership, use the clone method. This creates a deep copy of the data.

Example 3: Cloning Data

fn main() {
let s1 = String::from("Rust");
let s2 = s1.clone(); // Creates a deep copy of s1
println!("s1: {}, s2: {}", s1, s2); // Both variables are valid
}

Output:

s1: Rust, s2: Rust

Borrowing in Rust

Rust allows you to “borrow” a value instead of taking ownership. Borrowing is done using references (&), which come in two forms:

  1. Immutable Borrowing (&T)
  2. Mutable Borrowing (&mut T)

Example 4: Immutable Borrowing

fn main() {
let s = String::from("Hello");
let len = calculate_length(&s); // Borrowing s
println!("Length: {}", len);
}

fn calculate_length(s: &String) -> usize {
s.len() // Using borrowed value
}

Output:

Length: 5

Example 5: Mutable Borrowing

Mutable references allow you to modify the borrowed value.

fn main() {
let mut s = String::from("Hello");
change(&mut s); // Borrowing s mutably
println!("{}", s);
}

fn change(s: &mut String) {
s.push_str(", world!"); // Modifying borrowed value
}

Output:

Hello, world!

Rules of Borrowing

  1. You can have either one mutable reference or multiple immutable references, but not both at the same time.
  2. References must always be valid.

Example 6: Borrowing Rules

fn main() {
let mut s = String::from("Rust");

// Multiple immutable references are allowed
let r1 = &s;
let r2 = &s;
println!("r1: {}, r2: {}", r1, r2);

// Only one mutable reference is allowed
let r3 = &mut s;
println!("r3: {}", r3);

// Uncommenting the following lines will cause a compile-time error
// let r4 = &s; // Error: Cannot borrow as immutable while mutable borrow exists
}

Ownership and Functions

Passing values to functions also transfers ownership unless you use references.

Example 7: Ownership and Functions

fn main() {
let s = String::from("Hello");
take_ownership(s); // Ownership is moved to the function
// println!("{}", s); // Error: s is no longer valid
}

fn take_ownership(s: String) {
println!("{}", s);
}

Return Values and Ownership

Functions can return ownership, allowing the caller to take ownership of the value.

Example 8: Returning Ownership

fn main() {
let s = gives_ownership();
println!("Returned string: {}", s);
}

fn gives_ownership() -> String {
String::from("Ownership")
}

Leave a Comment

BoxofLearn