
How Rust Helped Me Write Better Code
Before I learned Rust, I thought I had a good handle on writing clean and reliable code. But Rust challenged me on how I think about ownership, modeling, errors, and safety - and in the process, it made me a better developer.
1. Ownerships
Before, I didn’t really think much about a variable’s scope. I would simply create a variable and decide whether it should be constant or mutable — and that was it.
But when I started learning Rust, I realized there are a few important things to consider before declaring a variable:
- Who owns the data?
- Should I borrow it, clone it, or move it?
- When does it go out of scope?
Now, I think more carefully about a value’s lifetime and responsibility, which leads to more performant and better-structured code.
Example:
fn greet(name: String) {
println!("Hello, {}", name);
}
fn main() {
let name = String::from("Anthony");
greet(name); // Ownership moved here
println!("Hello again, {}", name); // Error: value was moved
}
In this example, name
is moved into the greet
function — meaning the ownership is transferred. After that, we can no longer use name
anymore. But what if we still want to use it? What if we just want to pass the value without giving up ownership?
fn greet(name: &String) {
println!("Hello, {}", name);
}
fn main() {
let name = String::from("Anthony");
greet(&name); // Borrowed name
println!("Hello again, {}", name); // Will work
}
In this updated example, we use &name
instead of name
to borrow the value. We are letting the greet
function to borrow it, so we can still use the name
afterwards.
2. Structs
Structs in Rust is similar to TypeScript interfaces or types — where we can define our data model. But Rust’s structs are much more powerful. Not only we can define fields with specific types, but we can also attach methods and custom behaviour directly to the struct.
struct User {
id: u32,
name: String,
is_active: bool,
}
impl User {
fn greet(&self) {
if self.is_active {
println!("Hello, {}!", self.name);
} else {
println!("Account is inactive.");
}
}
}
fn main() {
let user = User {
id: 1,
name: String::from("Anthony"),
is_active: false,
};
user.greet();
}
In this example:
- We define a
User
struct. - We use
impl
to attach a methodgreet()
that will display a message depending on the user’s status. - Then, create an instance out of the struct and call the
greet()
method. - If the user is active, it displays
Hello, Anthony!
, otherwise it displaysAccount is inactive.
.
3. Write Safer Code
Rust forces us to write safer code by making us handle errors explicitly instead of letting us ignore potential errors or null values.
Two core features associated with it:
- Option
- used when a value might not be present. - Result<T, E> - used when a value might fail.
struct User {
id: u32,
name: String,
is_active: bool,
}
impl User {
fn get_greetings(&self) -> Result<String, String> {
if self.is_active {
Ok(format!("Hello, {}!", self.name))
} else {
Err("Account is inactive.".to_string())
}
}
}
fn find_user_by_id(id: u32) -> Option<User> {
if id == 1 {
Some(User {
id,
name: String::from("Anthony"),
is_active: true,
})
} else {
None
}
}
fn main() {
let user_id = 12;
match find_user_by_id(user_id) {
Some(user) => match user.get_greetings() {
Ok(message) => println!("{}", message),
Err(err) => println!("Error: {}", err),
},
None => println!("User not found."),
}
}
What is happening is that:
- The script will find a user by the id provided.
- Rust forces us to handle both cases - if we found a user or not.
- If the user is found, we try to get their greeting based on their status.
- If the user is active, the script will run normally and displays
Hello, Anthony!
. - But if the user is not active, the script throws error and displays
Error: Account is inactive.
.
In this example, we can see how Rust makes us handle potential errors and missing values explicitly. It shows that Rust enforces handling situations that could potentially occur, rather than letting us ignore them.
4. Compiler
Due to Rust’s complexity, learning the language without a guide would be much harder — especially for beginners.
Unlike other languages where error messages are vague or hard to understand, Rust’s compiler gives meaningful errors alongside with helpful suggestions. It doesn’t just tell you what’s wrong but tells you how to fix it as well.
I personally learned a lot about ownership and borrowing through it. It felt like having a built-in mentor reviewing my code. It may be strict but it makes learning enjoyable and rewarding.
5. Final Thoughts
Learning Rust is a challenge — it has a deep learning curve. But Rust’s design is full of amazing features that help us become better developers by writing safer and more reliable code. There’s so much to learn, and it’s absolutely worth it.