DEV Community

Subesh Yadav
Subesh Yadav

Posted on

πŸ¦€ Day 4 of #100DaysOfRust – Fixing Ownership Errors and Understanding Slices

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[..]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

πŸ” Range Syntax Shortcuts

&s[..]     // whole string
&s[3..]    // from index 3 to end
&s[..5]    // from start to index 5
Enter fullscreen mode Exit fullscreen mode

πŸ“š String Slices as Parameters

Instead of &String, it's idiomatic to use &str as the input type:

fn first_word(s: &str) -> &str
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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

rustlang #100DaysOfRust #buildinpublic #learninpublic

Top comments (0)