Today was an intense deep dive into two core Rust concepts:
- Fixing Ownership and Borrowing Errors
 - Understanding and Using Slices
 
π Fixing Ownership Errors in Rust
Learning how to fix ownership errors is a vital Rust skill. When the borrow checker rejects your code, understanding why and how to respond is key.
π Returning a Reference to the Stack
Trying to return a reference to a value that goes out of scope (e.g., inside a function) causes an error because the value is dropped when the function ends.
Fixes include:
- Returning the owned String instead of &String
 - Returning a string literal with a 'static lifetime
 - Using reference-counted pointers like Rc
 - Letting the caller provide a mutable reference to populate
 
β Not Enough Permissions (Immutable vs Mutable)
Attempting to mutate an immutable reference causes a compile error. For example, using .push() on a &Vec is not allowed.
Fixes include:
- Using a mutable reference &mut Vec (not ideal for non-mutating APIs)
 - Taking ownership of the data (not idiomatic for Vec or String)
 - Cloning the vector before mutation
 - Avoiding mutation entirely and appending the suffix to the final result instead
 
π Aliasing and Mutating a Data Structure
Holding a reference to part of a structure while trying to mutate the structure can cause conflicts. This is due to the possibility that mutation may invalidate previous references.
Fixes include:
- Cloning the data before mutation
 - Splitting mutation and reference usage into separate scopes
 - Extracting only the necessary data (like length) instead of the reference
 
π€ Copying vs Moving from Collections
With types like String (non-Copy), you cannot move the value out of a shared reference. Rust prevents you from accidentally causing double-free bugs.
Fixes include:
- Using .clone() to explicitly duplicate the data
 - Borrowing the value immutably with &String
 - Using Vec::remove() to safely move data out
 
β οΈ Safe Program Getting Rejected
Sometimes Rust rejects perfectly safe code due to limitations in borrow tracking. For example, borrowing a tuple field through a helper function may make the entire tuple seem borrowed.
Fixes include:
- Inlining the borrow instead of using a function
 - Splitting the data structure or using cells if needed (e.g., RefCell)
 
π Mutating Different Array Elements
Rust sees array indices like a[1] and a[2] as possibly overlapping and disallows simultaneous mutable and immutable borrows.
Fixes include:
- Using methods like slice::split_at_mut()
 - Ensuring mutations and reads happen in separate scopes
 - Avoiding raw pointer and unsafe unless absolutely necessary
 
βοΈ The Slice Type in Rust
Slices let you reference a part of a collection (like a string or array) without copying it. They are non-owning references and include length metadata β making them "fat pointers".
β¨ Motivation for Slices
Functions like first_word return an index into a String, but that index can become invalid if the string is modified. This creates bugs even in safe-looking code.
β Slice to the Rescue
With slices, you return a reference to a portion of the string:
fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}
This ties the returned reference to the actual string and ensures that the string cannot be mutated while the slice exists.
π§ Borrowing Rules with Slices
Creating a slice from a string borrows it immutably. If you later try to mutate the original string, Rust will produce an error.
let mut s = String::from("hello");
let slice = &s[..];
s.push_str(" world"); // β Error
π Range Syntax Shortcuts
&s[..]     // whole string
&s[3..]    // from index 3 to end
&s[..5]    // from start to index 5
π String Slices as Parameters
Instead of &String, it's idiomatic to use &str as the input type:
fn first_word(s: &str) -> &str
This makes the function work with:
- &String
 - string slices
 - string literals
 
π§© Other Slice Types
Slices also work with arrays and vectors:
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
This is useful when you want to access subranges without copying data.
β Summary
π Fixing Ownership Errors:
- Understand why a program is rejected: Is it unsafe or safe but rejected?
 - Use cloning, ownership transfer, or slice-based strategies to fix issues.
 - Avoid unnecessary mutations or unexpected side effects in function APIs.
 - Respect the borrow checkerβs rules on lifetimes and permissions.
 
βοΈ Working with Slices:
- Slices are safer and easier to use than index tracking.
 - They prevent bugs by tying the lifetime of the reference to the data.
 - Slices work with strings, arrays, and more.
 - Using &str makes functions more flexible and idiomatic.
 
That's a wrap for Day 4! Understanding these subtle but powerful concepts really builds the foundation for safe, performant Rust code. See you in Day 5! πͺ
Follow my journey on Twitter @subeshDev
    
Top comments (0)