What I learned from my first week in Rust
Source: Dev.to
How to learn Rust
Some frequently cited resources for learning Rust are:
- The Rust Programming Language – the official Rust book, which covers the language comprehensively.
- Rustlings – a collection of exercises that help you learn Rust by fixing small code snippets.
- Rust by Example – interactive, runnable code examples with explanations.
I personally found Rust by Example the most helpful, as it meshed well with my hands‑on learning style. It reminded me of the “Tour of Go” which was the primary resource I used to learn Go. Choose the resource that best fits your learning style.
If you prefer video resources, I also found freeCodeCamp’s Rust course on YouTube useful, though I only watched a small part of it.
I used ChatGPT extensively to explain concepts in depth and to help me understand compiler errors, refactor code, etc. At the learning stage, the key is to not let AI write the code for you—make sure you process everything it says and understand it fully. Remember that AI can hallucinate, so take its output with a grain of salt.
Distinctive features of Rust
Syntax
Rust uses a double colon :: as a path separator for modules, namespaces, etc., unlike the dot . used in Go, Python, and JavaScript.
Ownership
Ownership is a core concept unique to Rust. Each value has exactly one owner, and the value is dropped when the owner goes out of scope.
let s = String::from("hello");
Here s owns the String. Moving ownership:
let s2 = s; // s is moved into s2
// s can no longer be used
Passing a variable to a function transfers ownership to that function:
fn main() {
let s = String::from("hello");
takes_ownership(s); // ownership moves into the function
// println!("{}", s); // ❌ ERROR: s is no longer valid here
}
fn takes_ownership(value: String) {
println!("Got: {}", value);
} // `value` goes out of scope here → String is dropped
Returning a value also transfers ownership back to the caller.
To avoid transferring ownership, you can borrow a value by passing a reference:
let s = String::from("hello");
foo(&s); // ownership is NOT transferred
Mutable references allow modification:
let mut s = String::from("hello");
foo(&mut s);
Rust permits any number of immutable references or a single mutable reference at a time, but not both, preventing data races at compile time.
Error handling
Rust forces explicit error handling via the Result type. The ? operator propagates errors succinctly.
fn foo() -> Result<(), std::io::Error> {
// …
Ok(())
}
fn bar() -> Result<(), std::io::Error> {
let data = foo()?; // propagates error if any
// use `data`
Ok(())
}
If foo returns Ok(value), value is assigned to data; otherwise, the error is returned early from bar. This replaces the more verbose error‑checking pattern common in Go:
data, err := foo()
if err != nil {
return "", err
}
Macros
Macros enable metaprogramming by expanding into code at compile time. They are invoked with a trailing !, e.g., println!.
A simple macro example using macro_rules!:
macro_rules! add_one {
($x:expr) => {
$x + 1
};
}
fn main() {
let a = add_one!(5);
println!("{}", a); // prints 6
}
The compiler expands add_one!(5) to 5 + 1 before compilation.
Traits
Traits are similar to interfaces in Go or other OOP languages. They define a set of required methods that a type can implement.
trait Greet {
fn greet(&self) -> String;
}
struct Person {
name: String,
}
impl Greet for Person {
fn greet(&self) -> String {
format!("Hello, my name is {}!", self.name)
}
}
Building a simple HTTP server in Rust
To test my Rust knowledge, I challenged myself to write a basic HTTP server. You can see the complete code [here].