Rust’s Ownership system is designed for memory safety because memory is automatically and safely managed without needing a garbage collector.
Java and Python languages rely on garbage collection, but Rust uses ownership to automatically clean up memory when it’s no longer needed.
In simple terms, every value in Rust has a single owner, and when the owner goes out of scope, the value is automatically dropped.
Why Ownership Matters in Rust?
Ownership is important because memory bugs, such as double frees, dangling pointers, or data races, are common in low-level languages like C/C++. Rust’s ownership system solves these issues at compile time and making Rust safe and efficient.
Three golden rules of ownership in Rust:
- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped automatically.
Ownership Example
Understand this ownership example:
fn main() {
let name = String::from("Aarav");
greet(name); // ownership moves here
// println!("{}", name); // Error: value moved
}
fn greet(person: String) {
println!("Hello, {}!", person);
} // person goes out of scope and memory is freed here
Code explanation:
- let name = String::from(“Aarav”); = This line creates a String and gives ownership of it to the variable name.
- When you call greet(name) = you are passing ownership of that string to the person parameter inside the greet function.
- Inside the greet function, the string is used: println!(“Hello, {}!”, person);
- But now see the important part: When the function ends, the variable “person” goes out of scope, and Rust automatically frees the memory for the string.
What is Ownership Move in Rust?
Ownership move means transferring control of a value from one variable to another. After the move, the original variable can no longer use that value. This method helps to manage memory automatically and safely without a garbage collector.
Simple example:
fn main() {
let animal = String::from("Elephant");
let pet = animal; // move occurs
// println!("{}", animal); // Error: value moved
println!("{}", pet); // Works
}
Ownership with Function Returns
In Rust, a Function not only receives data, but it can also return data with ownership.
When you return a value from a function, that value’s ownership will be transferred to the function caller. Let’s see the example:
fn main() {
let value = give_value(); // function se ownership milti hai
println!("Returned: {}", value); // use kar sakte hai
}
fn give_value() -> String {
let data = String::from("Rust");
data // yahan ownership return ho jaati hai
}
In this code, when we return the data, so that’s ownership will be passed to the main function’s variable.
Real-Life Example: Rust Config File Reader with Borrowing
use std::fs::File;
use std::io::{self, Read};
fn main() -> io::Result<()> {
let mut config_file = File::open("config.txt")?; // Unique file name
let mut config_data = String::new();
config_file.read_to_string(&mut config_data)?; // Reading file content
display_summary(&config_data); // Borrowed reference
println!("Main ends. File handled safely.");
Ok(())
}
fn display_summary(text: &String) {
let lines = text.lines().count();
println!("Config Summary:\n- Total lines: {}\n- Preview: {}", lines, text);
}
This mini project reads a one text file (like config.txt), then handles its content safely using borrowing, and then prints a small summary of that file.
This example teaching you:
- Open the file in Rust
- Read the file content in string
- Use the data through references without ownership.
- Simple analysis using .lines() and .count() methods.
Exercise For Practice
Write a program that asks the user for a fruit name, passes it to a function to display it, and ensures that ownership is correctly handled without error
Below we provided the solution, but first write it by yourself, then use this code:
use std::io;
fn main() {
println!("Enter your favorite fruit:");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read input");
// Remove newline character
let fruit = input.trim().to_string();
show_fruit(&fruit); // borrow the fruit
println!("Back in main, your fruit is: {}", fruit); // still usable
}
fn show_fruit(name: &String) {
println!("You entered: {}", name);
}
Try to understand this code program by yourself.
Disclaimer: Some concepts in this article are based on Rust’s official documentation (https://doc.rust-lang.org), rewritten in simple and unique language for learning purposes.