Welcome back, fellow Rustaceans 🦀

In our previous article, we explored Rust's ownership model and how it manages heap-allocated structures without relying on a garbage collector. While the ownership model is powerful, it can present challenges that require careful handling of data references. Today, we'll examine some common pitfalls of Rust's ownership model and how we can address them effectively.

Rust’s ownership model ensures memory safety without needing a garbage collector. Each value in Rust has a single owner, and when the owner goes out of scope, the value is dropped. This prevents issues like dangling pointers and double frees. Trust me, despite how simple this may sound, it can be a real challenge when you start programming with Rust. You'll likely spend countless hours battling with the Rust compiler, potentially wearing out your keyboard and mouse, only to end up feeling frustrated like this.

Why why why oh thats why meme template : r/memes

Consider the following example where we pass a variable to a function:

fn main() {
    let s = String::from("Hello, Rust!");
    take_ownership(s);
    println!("{}", s); // This will cause a compilation error
}

fn take_ownership(s: String) {
    println!("{}", s);
}

In this code, the take_ownership function takes the String variable s and prints it. It's return and does the same in the main . When s is passed to take_ownership, its ownership is moved from main to take_ownership. As a result, s is no longer accessible in main, leading to a compilation error when we try to print it. If you're an experienced C programmer, you might assume everything is fine and that the code will simply print "Hello, Rust" twice. However, keep in mind that Rust operates differently. Let's give it a try and see what happens when we compile it.

See, the Rust compiler is not Happy. Let’s take a closer look at the compiler’s message. It points out that at line 4, where we attempt to print s a second time with println!("{}", s);, we are trying to borrow a value that has already been moved. This should remind you of what we discussed last week. In this code, the take_ownership function takes ownership of the String variable s. Once s is passed to take_ownership, its ownership is transferred from main to take_ownership. Consequently, s is no longer accessible in main, resulting in a compilation error when we try to print it.

So, does this mean we can't use a variable once we pass it to a function? That's Nuts! One way to address this is by returning the variable to the caller.

fn main() {
    let s = String::from("Hello, Rust!");
    let s = take_ownership(s);
    println!("{}", s); // This works now
}

fn take_ownership(s: String) -> String {
    println!("{}", s);
    s
}

It Works! In the modified code, take_ownership returns the String back to main, allowing s to be used again. I Know what you are thinking, It's not a good solution. While it works, it can become cumbersome, especially with multiple variables and function calls. Is there a better solution? Yes, there is: we can borrow the variable. Let’s explore how borrowing can help us.

Borrowing and References

To address the cumbersomeness of moving ownership and returning variables, Rust provides a feature called borrowing. Borrowing allows a function to use a variable without taking ownership of it. This is achieved through references.

References

A reference is a way of referring to some value without taking ownership. References are created using the & symbol.

Example: Borrowing with References

Let's revisit our previous example using references to borrow the String instead of taking ownership:

fn main() {
    let s = String::from("Hello, Rust!");
    borrow_ownership(&s);
    println!("{}", s); // This works now
}

fn borrow_ownership(s: &String) {
    println!("{}", s);
}

In this code, the borrow_ownership function takes a reference to the String variable s (&String). This allows borrow_ownership to use s without taking ownership. The original s in main remains valid, so we can print it after the function call.

Great! This works as well, and we don't need to return the variable to the original function.

Conclusion

Today, we explored the concept of borrowing in Rust, starting with ownership and how passing variables to functions moves ownership. We saw how returning variables can solve ownership issues but can become cumbersome. Then, we delved into references and borrowing, showing how they allow functions to use variables without taking ownership..

Understanding borrowing is crucial for writing efficient and safe Rust code. In the upcoming articles we will have a much deeper look at how rust ensures safety around references and some of the restrictions around it.

Stay tuned for our next article. For more articles on Rust, check out InPyjama.