Rust's ownership and borrowing system is a core concept for its robust memory management. In this blog post, we'll explore references in detail, building upon the fundamental principles of Rust ownership.
Understanding Ownership in Rust
Stack: Variables with fixed sizes, like integers, floats, or booleans, are stored on the stack. Stack allocation is incredibly efficient, as it's direct and fast.Heap: Variables with variable sizes, such as strings or vectors, are stored on the heap. Heap allocation is more flexible but slower due to the need to search for the right location in memory.
let x = 2; // Variable 'x' is stored on the stack
let y = x; // The value of 'x' is copied into 'y' (cheap copy on stack)
println!("x: {x}"); // Output: x: 2
println!("y: {y}"); // Output: y: 2
let a = "hello".to_string(); // Variable 'a' is stored on the heap
let b = a; // 'b' becomes the new owner of the data, 'a' becomes invalid
println!("{a}"); // Error: 'a' is invalid
References and Borrowing
let a = "hello".to_string();
let b = &a; // 'b' is a reference to 'a'
println!("a: {a}"); // Output: a: hello
println!("b: {b}"); // Output: b: hello
The Rules of Borrowing
References are always valid: A reference is only valid as long as its owner is valid. If the owner is moved or dropped, all references pointing to it become invalid.Mutability: References can be immutable or mutable. There are two types of references:Immutable References: These references can only read data. They cannot modify the value they point to.Mutable References: These references can modify the value of the data they point to.
let mut v = vec![0, 1, 2]; // 'v' is mutable
// Mutable Reference:
let u = &mut v;
u.push(3); // Modifying the content of 'v' through 'u'
println!("vector v: {v:?}"); // Output: vector v: [0, 1, 2, 3]
Only one mutable reference at a time: We can only have one mutable reference to a piece of data. Other references must be immutable.
let mut a = vec![0, 1, 2];
let b = &a; // Immutable Reference
let c = &a; // Immutable Reference
let d = &mut a; // Mutable Reference
d.push(3); // Modifying 'a' through 'd'
println!("{c}"); // Error: 'c' is invalid because there is a mutable reference 'd'
println!("{d:?}"); // Output: [0, 1, 2, 3]
Introducing Lifetimes
let s;
{
let t = 5;
s = &t; // 's' points to 't'
}
println!("{s}"); // Error: 't' is no longer valid
fn main() {
// LIFETIMES
//---------------------- 'a
let s; //|
//|
{ //|
//---------------'b |
let t = 5; //| |
//| |
s = &t //| |
//---------------'b |
} //|
//---------------------|'a
}
Lifetimes in Functions
fn example_1() -> &i32 { // Error: 'x' goes out of scope
let x = 2;
&x
}
fn example_2(x: &i32) -> &i32 { // Valid: 'x' remains valid
&x
}
fn example_3<'a>(x: &'a i32, y: &'a i32) -> &'a i32 { // Valid: 'x' or 'y' remains valid
&x
}
0 comments:
Post a Comment