DEV Community

Cover image for Rust for Beginners: 8 Practical Tips to Get Started
Olivia
Olivia

Posted on

Rust for Beginners: 8 Practical Tips to Get Started

Intro

I've been learning and writing Rust for about 7 years now, but I still remember the beginner frustration where everything felt really difficult and I was constantly fighting the compiler even to do basic things. Getting started with Rust can be challenging but teaching a lot of beginners over the last few years, I started to see some common pitfalls or patterns that you can start using today to improve your learning curve.

Tip 1: Use unwrap for Options and Results liberally at first

Options and Results are new for someone coming from a language without them. Focus on making stuff work first by unwrapping to make your code work. Then learn about how to deal with them idiomatically later. Also you're most likely writing some throw-away code initially, trying to make something work so error handling won't be your first priority, so I'd recommend unwrapping liberally, then doing a second pass to improve error handling, something like this.

impl Database {
    pub fn find_student(&self, name: &str) -> Option<&Student> {
        /// ...
    }
}

pub fn main() {
    // Load database with all students 
    let database = Database::new();

    // Find a specific student and print their id
    let student_id = database.find_student("Bob").unwrap().id;
    println!("Bob's id is {}", student_id);
}
Enter fullscreen mode Exit fullscreen mode

Then in the second iteration this code becomes something like this.

Once your code is working and you understand the error cases better, transition away from unwrap() in production code where unexpected panics could crash your application - that's when proper error handling with match, if let, or the ? operator becomes important for robustness. Here we use a match.

pub fn main() {
    // Load database with all students 
    let database = Database::new();

    // Find a specific student and print their id
    match database.find_student("Bob") {
        Some(bob) => println!("Bob's id is {}", bob.id);
        None => println!("Didn't find Bob");
    }
}
Enter fullscreen mode Exit fullscreen mode

Tip 2: Learn the difference between String and &str

This is fundamental to understand, and don't be afraid to use String only at first even if it's terribly inefficient. You can optimize later once you understand the concepts better.

Understanding slices will also help here - slices are essentially borrowed views into contiguous sequences of data. Think of &str as a string slice that references existing string data without owning it. For example, &s[0..5] creates a slice that references the first 5 characters of string s without copying them. This concept applies to both strings and arrays/vectors (where &[T] is an array slice). Start by using them to reference parts of existing data, and you'll gradually understand how they help with memory efficiency.

fn main() {
    // String - owned, heap-allocated, mutable
    let mut owned_string = String::from("Hello");
    owned_string.push_str(" World"); // Can modify

    // &str - borrowed reference to string data, immutable
    let string_slice: &str = "Hello World"; // String literal
    let borrowed_slice: &str = &owned_string; // Borrowing from String

    // Function that takes &str can accept both
    print_message(&owned_string); // String automatically converts to &str
    print_message(string_slice);  // &str passed directly
}

fn print_message(msg: &str) {
    println!("{}", msg);
}
Enter fullscreen mode Exit fullscreen mode

Tip 3: Clone liberally (but do not Copy)

At first you'll get the compiler shouting at you a lot because you're moving stuff without knowing you're moving it. Add a Clone derive annotation, call .clone() and move on to the next problem! Then as a second pass come back and optimize.

#[derive(Clone)]
struct Student {
    name: String,
    id: u32,
}

fn process_student(student: Student) {
    println!("Processing student: {}", student.name);
}

fn main() {
    let alice = Student {
        name: "Alice".to_string(),
        id: 12345,
    };

    process_student(alice.clone()); // Clone so we don't move alice
    println!("Alice is still available: {}", alice.name); // This works!
}
Enter fullscreen mode Exit fullscreen mode

Tip 4: Avoid Storing References in Structs

As soon as you do this you'll need to get into lifetimes and if you're still learning the basics, I'd avoid this. Just store owned types, and pass everything else as arguments to functions.

// AVOID
struct Course<'a> {
    student: &'a Student, // Borrowed, needs explicit lifetimes
}

// GOOD
struct Course {
    student: Student, // Owns the data
}
Enter fullscreen mode Exit fullscreen mode

Tip 5: Avoid linked lists and trees

As soon as you get into this you need to understand borrowing and Box/Rc/Arc which you will struggle with as a beginner. Once you feel comfortable with the basics, then dive deeper into these advanced data structures. There is even a whole book about writing many flavours of linked lists: Learn Rust With Entirely Too Many Linked Lists which I'd recommend reading only after you learn at least some of the core rust pointer types.

Tip 6: Consider Ordering and Use Scope Blocks

This is my favourite trick, if you plan to do multiple mutations, or a read, write then read again, ordering plus creating a new scope block for each can save the day as it releases the borrow.

struct Student {
    name: String,
    grade: u8,
}

impl Student {
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            grade: 100,
        }
    }

    pub fn grade_mut(&mut self) -> &mut u8 {
        &mut self.grade
    }

    pub fn name(&self) -> &str {
        &self.name
    }
}

fn main() {
    let mut student = Student::new("Alice");

    {
        let grade = student.grade_mut();
        *grade += 1;
    } // grade's mutable borrow is dropped here

    let name = student.name();  // Now this works
    println!("Name: {}", name);
}

/// WORKS!!
Enter fullscreen mode Exit fullscreen mode

Compare this to the problematic version:

fn main() {
    let mut student = Student::new("Alice");

    let grade = student.grade_mut(); // First mutable borrow
    let name = student.name();       // Try to borrow immutably immediately
    *grade += 1;                     // Still using mutable borrow

    println!("Name: {}, Grade: {}", name, grade);
}

// Error: cannot borrow `student` as immutable because it is also borrowed as mutable
Enter fullscreen mode Exit fullscreen mode

Tip 7: Use Option::take() to Move Values Safely

If you need to move a value out of a struct, Option<T>::take() prevents borrowing conflicts:

struct Database {
    student: Option<Student>,
}

impl Database {
    fn remove_student(&mut self) -> Option<Student> {
        self.student.take() // ✅ Replaces with None, avoiding borrowing issues
    }
}

let mut db = Database { student: Some(Student::new("Charlie")) };
let removed_student = db.remove_student(); // ✅ Student is safely moved out
Enter fullscreen mode Exit fullscreen mode

Tip 8: Pass Data as Function Arguments Instead of Storing It

The extra step of storing owned values is not storing values at all. Instead of holding a reference/owned value inside a struct, pass data when needed, and when it makes sense. It's also easier to pass references around without dealing with lifetimes.

struct Course;

impl Course {
    fn process(student: &mut Student) {
        println!("{} is processed!", student.name);
    }
}

let alice = Student::new("Alice");
Course::process(&mut alice); // ✅ Passes a reference instead of storing it
Enter fullscreen mode Exit fullscreen mode

Conclusion

These practical tips worked for me initially, and as I got more comfortable with the language, I started going deeper into each of these topics and learning how to do something in the most idiomatic and efficient way.

Hope it helped you learn 1% more Rust today, I write a newsletter like this every Thursday subscribe to receive this, and follow me on Bluesky 🦋 🦀

Top comments (1)

Collapse
 
masterdevsabith profile image
Muhammed Sabith

Maybe in the future it would be helpful! Saving right now