Rust Ownership
Overview
Ownership is Rust's most unique and important feature, enabling Rust to guarantee memory safety without a garbage collector. This chapter will delve into how the ownership system works and how to use it.
🎯 Ownership Basic Concepts
Ownership Rules
rust
// Three rules of Rust ownership:
// 1. Every value in Rust has a variable called its owner
// 2. A value can have only one owner at any given time
// 3. When the owner (variable) goes out of scope, the value is dropped
fn ownership_rules() {
// Rule 1: Each value has an owner
let s = String::from("hello"); // s is the owner of "hello"
// Rule 2: Only one owner at the same time
let s2 = s; // Ownership transfers from s to s2
// println!("{}", s); // Compile error! s is no longer valid
println!("{}", s2); // OK, s2 is now the owner
} // Rule 3: s2 goes out of scope, memory is freedVariable and Data Interaction
Move
rust
fn move_semantics() {
// Basic types: copy
let x = 5;
let y = x; // x is copied to y, both are valid
println!("x: {}, y: {}", x, y);
// Complex types: move
let s1 = String::from("hello");
let s2 = s1; // s1's ownership moves to s2
// println!("{}", s1); // Compile error! s1 is invalid
println!("{}", s2);
// Explanation of why move happens
// String structure on the stack:
// name value
// ptr -> pointer to heap data
// len -> string length
// capacity -> allocated capacity
// If Rust allowed copying, there would be two pointers to the same heap memory
// When both variables go out of scope, they would both try to free memory, causing a double free error
}Clone
rust
fn clone_semantics() {
let s1 = String::from("hello");
let s2 = s1.clone(); // Deep copy, creates new heap data
println!("s1: {}, s2: {}", s1, s2); // Both are valid
// Cost of cloning
let large_string = "a".repeat(1_000_000);
let cloned = large_string.clone(); // Expensive operation
println!("Original length: {}, Cloned length: {}", large_string.len(), cloned.len());
}Copy
rust
fn copy_trait() {
// Types implementing Copy trait are copied instead of moved
let x = 5;
let y = x; // Copy, not move
println!("x: {}, y: {}", x, y);
// Copy types include:
// - All integer types
// - Boolean type
// - Floating point types
// - Character type
// - Tuples (if all elements are Copy)
let tuple = (1, 2.0, 'a');
let tuple_copy = tuple; // Copy
println!("Original: {:?}, Copy: {:?}", tuple, tuple_copy);
// Cannot implement both Copy and Drop
// If a type implements Drop trait, it cannot implement Copy trait
}
// Copy for custom types
#[derive(Copy, Clone, Debug)]
struct Point {
x: i32,
y: i32,
}
fn custom_copy() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Copy, not move
println!("p1: {:?}, p2: {:?}", p1, p2);
}🔄 Ownership and Functions
Passing Values to Functions
rust
fn ownership_and_functions() {
let s = String::from("hello");
takes_ownership(s); // s's value moves into the function
// println!("{}", s); // Compile error! s is no longer valid
let x = 5;
makes_copy(x); // x's value is copied into the function
println!("{}", x); // x is still valid
}
fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{}", some_string);
} // some_string goes out of scope and is dropped
fn makes_copy(some_integer: i32) { // some_integer comes into scope
println!("{}", some_integer);
} // some_integer goes out of scope, nothing special happensReturn Values and Scope
rust
fn return_values_and_scope() {
let s1 = gives_ownership(); // Function return value transfers to s1
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); // s2 moves into function, return value transfers to s3
println!("s1: {}, s3: {}", s1, s3);
// println!("{}", s2); // Compile error! s2 has been moved
}
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string // Return, ownership moves to calling function
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // Return, ownership moves to calling function
}Complex Ownership Transfers
rust
fn complex_ownership_transfer() {
let mut data = vec![1, 2, 3, 4, 5];
// Functions can return multiple values
let (data, length) = calculate_length_and_return(data);
println!("Data: {:?}, Length: {}", data, length);
// Use tuples to return multiple values
let result = process_data(data);
match result {
Some((processed_data, summary)) => {
println!("Processed data: {:?}", processed_data);
println!("Summary: {}", summary);
}
None => println!("Processing failed"),
}
}
fn calculate_length_and_return(vec: Vec<i32>) -> (Vec<i32>, usize) {
let length = vec.len();
(vec, length) // Return tuple, ownership transfers
}
fn process_data(mut data: Vec<i32>) -> Option<(Vec<i32>, String)> {
if data.is_empty() {
return None;
}
// Modify data
for item in &mut data {
*item *= 2;
}
let summary = format!("Processed {} elements", data.len());
Some((data, summary))
}🏗️ Ownership and Data Structures
Struct Ownership
rust
#[derive(Debug)]
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn struct_ownership() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// Move entire struct
let user2 = user1;
// println!("{:?}", user1); // Compile error! user1 has been moved
println!("{:?}", user2);
// Partial move
let user3 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername"),
active: true,
sign_in_count: 1,
};
let email = user3.email; // Only move email field
let username = user3.username; // Only move username field
// println!("{:?}", user3); // Compile error! Some fields have been moved
// But can access non-moved fields
println!("Active status: {}, Sign in count: {}", user3.active, user3.sign_in_count);
}Enum Ownership
rust
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn enum_ownership() {
let msg1 = Message::Write(String::from("hello"));
let msg2 = msg1; // Move
// println!("{:?}", msg1); // Compile error
println!("{:?}", msg2);
// Ownership transfer during matching
match msg2 {
Message::Write(text) => {
println!("Write message: {}", text);
// text ownership is acquired here
}
_ => {}
}
// println!("{:?}", msg2); // Compile error! msg2 content has been moved
}Collection Type Ownership
rust
fn collection_ownership() {
// Vec ownership
let mut vec1 = vec![1, 2, 3];
let vec2 = vec1; // Move
// println!("{:?}", vec1); // Compile error
// Remove element from Vec
let mut vec3 = vec![String::from("hello"), String::from("world")];
let first = vec3.remove(0); // Move out first element
println!("Removed element: {}", first);
println!("Remaining Vec: {:?}", vec3);
// HashMap ownership
use std::collections::HashMap;
let mut map = HashMap::new();
let key = String::from("favorite");
let value = String::from("blue");
map.insert(key, value); // key and value ownership transfers to map
// println!("{} {}", key, value); // Compile error
// Retrieve ownership
if let Some(color) = map.remove("favorite") {
println!("Favorite color: {}", color);
}
}🔀 Ownership Patterns
RAII Pattern
rust
use std::fs::File;
use std::io::prelude::*;
fn raii_pattern() {
// Resource Acquisition Is Initialization (RAII)
{
let mut file = File::create("temp.txt").expect("Failed to create file");
file.write_all(b"Hello, RAII!").expect("Failed to write");
// File is automatically closed here because file goes out of scope
}
// File has already been closed
// Custom RAII type
let _guard = ResourceGuard::new("Important resource");
// guard will automatically clean up resources when it goes out of scope
}
struct ResourceGuard {
name: String,
}
impl ResourceGuard {
fn new(name: &str) -> Self {
println!("Acquiring resource: {}", name);
Self {
name: name.to_string(),
}
}
}
impl Drop for ResourceGuard {
fn drop(&mut self) {
println!("Releasing resource: {}", self.name);
}
}Builder Pattern and Ownership
rust
#[derive(Debug)]
struct Config {
host: String,
port: u16,
ssl: bool,
}
struct ConfigBuilder {
host: Option<String>,
port: Option<u16>,
ssl: Option<bool>,
}
impl ConfigBuilder {
fn new() -> Self {
Self {
host: None,
port: None,
ssl: None,
}
}
// Consume self, return new self
fn host(mut self, host: String) -> Self {
self.host = Some(host);
self
}
fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
fn ssl(mut self, ssl: bool) -> Self {
self.ssl = Some(ssl);
self
}
// Consume self, return final product
fn build(self) -> Result<Config, String> {
let host = self.host.ok_or("Missing hostname")?;
let port = self.port.unwrap_or(80);
let ssl = self.ssl.unwrap_or(false);
Ok(Config { host, port, ssl })
}
}
fn builder_pattern_ownership() {
let config = ConfigBuilder::new()
.host(String::from("localhost"))
.port(8080)
.ssl(true)
.build()
.expect("Failed to build config");
println!("Config: {:?}", config);
}⚡ Performance Considerations
Avoiding Unnecessary Moves
rust
fn performance_considerations() {
// Bad example: unnecessary move
let data = vec![1, 2, 3, 4, 5];
let result = process_vec_bad(data);
// data cannot be used anymore
// Better example: use references
let data = vec![1, 2, 3, 4, 5];
let result = process_vec_good(&data);
println!("Original data still available: {:?}", data);
println!("Processing result: {}", result);
// Only move when modification is needed
let mut data = vec![1, 2, 3, 4, 5];
modify_vec(&mut data);
println!("Modified data: {:?}", data);
}
fn process_vec_bad(vec: Vec<i32>) -> i32 {
vec.iter().sum() // Doesn't need ownership but moved anyway
}
fn process_vec_good(vec: &[i32]) -> i32 {
vec.iter().sum() // Only needs to read, use reference
}
fn modify_vec(vec: &mut Vec<i32>) {
vec.push(6); // Needs to modify, use mutable reference
}Smart Ownership Management
rust
use std::rc::Rc;
use std::cell::RefCell;
fn smart_ownership() {
// Use Rc to share ownership
let data = Rc::new(vec![1, 2, 3, 4, 5]);
let data1 = Rc::clone(&data);
let data2 = Rc::clone(&data);
println!("Reference count: {}", Rc::strong_count(&data));
// Interior mutability
let shared_data = Rc::new(RefCell::new(vec![1, 2, 3]));
{
let mut data = shared_data.borrow_mut();
data.push(4);
}
println!("Shared data: {:?}", shared_data.borrow());
}📝 Chapter Summary
Through this chapter, you should have mastered:
Core Ownership Concepts
- ✅ Three basic rules of ownership
- ✅ Difference between move, clone, and copy
- ✅ Ownership and function calls
- ✅ Ownership transfer with return values
Advanced Ownership Patterns
- ✅ Struct and enum ownership
- ✅ Collection type ownership management
- ✅ RAII resource management pattern
- ✅ Ownership in builder pattern
Performance Optimization
- ✅ Avoid unnecessary moves and clones
- ✅ Choose appropriate parameter passing methods
- ✅ Smart pointer usage scenarios
- ✅ Memory management best practices
Best Practices
- Prefer references over moves
- Clone only when necessary
- Use RAII for resource management
- Consider ownership when designing APIs
Continue learning: Next Chapter - Rust References and Borrowing