DEV Community

Cover image for 3 Common Mistakes Beginners Make When Learning Rust
Francesco Ciulla
Francesco Ciulla

Posted on

3 Common Mistakes Beginners Make When Learning Rust

In 2024, the Stack Overflow Developer Survey voted Rust the most admired language. Rust's unique combination of performance, safety, and ergonomics has made it a popular choice for systems programming, web development, blockchain, and more.

However, as with any powerful tool, getting the most out of Rust requires a solid understanding of its fundamentals.

In this article, I want to share three common mistakes beginners often make when learning Rust and practical solutions to help you avoid them.

All the code is available and open source here: https://github.com/FrancescoXX/three-rust-mistakes

Let's get started!


1. Confusion Around the let Keyword

If you're from a JavaScript background, you might find Rust's let keyword familiar yet surprisingly different.

Example:

In JavaScript, you can reassign a variable declared with let:

let x = 10;
x = 20;  // Reassignment is allowed

console.log(x);  // Outputs: 20
Enter fullscreen mode Exit fullscreen mode

..But if we try to do the same in Rust, we'll encounter a compile-time error:

fn main() {
    let x = 10;
    x = 20;  // This will cause a compile-time error in Rust

    println!("{}", x);
}
Enter fullscreen mode Exit fullscreen mode

Why? In Rust, variables are immutable by default, meaning you can't reassign a value unless you explicitly declare the variable as mutable:

While let in both languages is used to declare variables, Rust’s strict handling of mutability and scope can be a source of confusion.

Solution:

To allow reassignment, you must declare the variable as mutable using the mut keyword:

fn main() {
    let mut x = 10;  // Declare x as mutable
    x = 20;  // Now reassignment is allowed

    println!("{}", x);  // Outputs: 20
}
Enter fullscreen mode Exit fullscreen mode

If you want to learn more about variables in Rust, check out this YouTube video


2. Misunderstanding Ownership and Borrowing

One of Rust's most distinctive and powerful features is its ownership model.

While this model ensures memory safety without a garbage collector, it can be tricky for beginners to grasp fully.

Misunderstanding ownership and borrowing often leads to common errors like "value borrowed after move" or "cannot borrow as mutable."

Example:

Consider the following code:

fn main() {
    let s = String::from("Hello, Rust!");
    let s2 = s;  // Ownership moves to s2, s is no longer valid

    println!("{}", s);  // This will cause a compile-time error
}
Enter fullscreen mode Exit fullscreen mode

In this example, when we assign s to s2, the ownership of the data is transferred to s2. As a result, s is no longer valid, and attempting to use it will cause a compile-time error!

Solution:

To avoid this mistake, you can borrow the value instead of transferring ownership.

Borrowing allows you to reference the original value without taking ownership, ensuring that the original variable remains valid:

fn main() {
    let s = String::from("Hello, Rust!");
    let s2 = &s;  // Borrowing s, s is still valid

    println!("{}", s);   // This will work
    println!("{}", s2);  // This will also work
}
Enter fullscreen mode Exit fullscreen mode

By using a &s reference , we can access the value without transferring ownership, allowing both s and s2 to coexist without errors.

Understanding ownership and borrowing is crucial to mastering Rust.

If you want a deeper dive into these concepts, check out this YouTube video on Rust's ownership model.


3. Ignoring Error Handling

While it's tempting to ignore error checks and use unwrap to quickly access values, doing so can lead to unexpected panics and runtime errors if something goes wrong.

Rust's Result type provides a powerful mechanism for handling errors safely and concisely, ensuring your program can gracefully deal with unexpected situations.

Example:

Consider the following code snippet that reads a file and prints its contents:

use std::fs::File;
use std::io::Read;

fn main() {
    let file = File::open("example.txt");
    let mut contents = String::new();
    file.unwrap().read_to_string(&mut contents).unwrap();  // This can panic if the file doesn't exist
    println!("{}", contents);
}
Enter fullscreen mode Exit fullscreen mode

In this example, the open method returns a Result type that represents the operation's success or failure.

Using unwrap on the Result can cause the program to panic if the file doesn’t exist or if there's another error.

Solution:

To handle errors properly, you can use the match expression to check the Result and handle both success and failure cases:

use std::fs::File;
use std::io::{self, Read};

fn main() {
    let file = File::open("example.txt");

    match file {
        Ok(mut f) => {
            let mut contents = String::new();
            match f.read_to_string(&mut contents) {
                Ok(_) => println!("{}", contents),
                Err(e) => eprintln!("Error reading file: {}", e),
            }
        }
        Err(e) => eprintln!("Error opening file: {}", e),
    }
}
Enter fullscreen mode Exit fullscreen mode

In this solution, instead of using unwrap, we handle the Result explicitly with a match statement (here is a video about the match statement).

This approach ensures errors are caught and handled, preventing unexpected panics and making the code more robust.

Using the ? Operator:

For even more concise error handling, Rust provides the ? operator, which can be used to propagate errors up the call stack:

use std::fs::File;
use std::io::{self, Read};

fn read_file() -> io::Result<String> {
    let mut file = File::open("example.txt")?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    match read_file() {
        Ok(contents) => println!("{}", contents),
        Err(e) => eprintln!("Error: {}", e),
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, the ? operator handles errors in a way that keeps the code clean and readable while still ensuring that errors are correctly managed.

If you want an introduction to Error Handling in Rust, check out this YouTube video.


Conclusion

Rust is an excellent language, but it takes time to get used to its quirks and unique features, like any new tool.

Remember, everyone makes mistakes, and it's normal while learning a new programming language, especially one as powerful and unique as Rust.

If you want to learn more about Rust, I have a Rust YouTube playlist where I post a complete course for beginner Rust developers.

All the code is available and open source here: https://github.com/FrancescoXX/three-rust-mistakes

Have fun writing Rust code!

Francesco

Top comments (0)