When Rust Gets Ugly | corrode Rust Consulting
Idiomatic Rust
When Rust Gets Ugly
by Matthias Endler
Published: 2026-07-17
In workshops I often see people getting frustrated with Rust.
Here’s some of the feedback I hear:
“The borrow checker rules make it hard to write code that compiles.”
“It’s overwhelming! The syntax is complex with too many symbols and operators. 1”
“It’s difficult to transition to Rust from .”
“The code is not satisfying to read, it feels clunky and verbose.”
From these frustrations, people often conclude that Rust is not for them and quit.
But after programming in Rust for 10 years, I think that your coding style has the biggest impact on how your Rust code will look and feel .
People often say Rust’s syntax is ugly, but I’d argue the syntax is the least interesting thing about Rust. The semantics (the bits and pieces the language provides to express your ideas and how those bits combine to build interesting things) are much more important.
The “ugliness” is only skin-deep; Rust’s beauty lies underneath the surface!<br>And with better semantics comes better syntax.
If you feel like you’re fighting the language, then there’s a chance that the language is speaking to you .<br>It tries to push you into a healthier direction, but if you resist, it will patiently wait until you give in.<br>The moment you start to listen to what Rust is trying to teach you, everything snaps into place; writing Rust feels effortless.
Better semantics unlock nicer syntax.<br>That means, the more you lean into the core mechanics behind Rust (traits, pattern matching, expressions, composition over inheritance, etc.), the more you can build on these concepts to write code that is readable and extensible.<br>The syntax takes a backseat. It gives way to semantics, which are much more important.
If you write Rust like you would write idiomatic code in another language, it will never feel right.<br>You have to embrace how Rust wants you to structure your code.<br>“You can write bad Java code in any language,” is a common saying, and I think it applies here as well.
Good Rust can tick all the boxes: it’s correct, readable, and maintainable.<br>Heck, I’d say it’s pretty, too!
Parsing Things
Let’s consider a simple example: parsing an .env file in Rust. How hard can it be?
DB_HOST=localhost<br>DB_PORT=5432
API_KEY=my_api_key<br>LOG_FILE=app.log<br>The goal is to parse the above content from a file called .env and return a data structure that contains the key-value pairs.<br>Child’s play.
I invite you to write your own version first.<br>Or at least take a second to think about the problem.
Then we’ll refactor a deliberately clunky first attempt into more idiomatic Rust and use that process to extract a general approach: read the standard library, lean on inference and types, handle errors explicitly, and split the problem into smaller parts.
A Painful First Attempt
A Rust learner will sit down and attempt to parse the above file.<br>They might come up with a solution like the one below, which is not too far from what I’ve seen recently.
use std::collections::HashMap;<br>use std::fs::File;<br>use std::io::Read;<br>use std::path::Path;
// Parse .env file into a HashMap<br>fn parse_config_filea>(path: &'a str) -> HashMapString, String> {<br>let p = Path::new(&path);<br>let mut file = File::open(&p).unwrap();<br>let mut bytes = Vec::new();<br>file.read_to_end(&mut bytes).unwrap();
let s = String::from_utf8_lossy(&bytes).to_string();
let lines_with_refs: Vec&'_ str> = s.split('\n').collect();
let mut idx = 0;<br>let mut cfg: HashMapString, String> = HashMap::new();
// Iter lines<br>while idx lines_with_refs.len() {<br>// Get the line reference and trim it<br>let lref = &lines_with_refs[idx];<br>let mut l = *lref;<br>l = l.trim();
// Skip empty lines<br>if l.len() == 0 {<br>idx += 1;<br>continue;
// Skip comments<br>if l.chars().next() == Some('#') {<br>idx += 1;<br>continue;
// Actual string splitting and trimming<br>let parts = l.split('=').collect::Vec&str>>();<br>let k: &str = parts[0].trim();
// Check if key is empty<br>if k.len() > 0 {<br>// We found a valid key. Insert into config<br>let v: &str = parts[1].trim();<br>cfg.insert(k.to_string(), v.to_string());<br>} else {<br>// This only happens if the line is malformed, so skip<br>println!("Error in line {:?}", parts);
// Process next line<br>idx += 1;
return cfg;
fn main() {<br>// Parse a `.env` file in the current directory.<br>let config = parse_config_file(".env");<br>println!("{config:#?}");<br>The code carries all the hallmarks of a beginner Rust programmer, possibly with a C/C++ background.
Littered with unwrap() calls
Unnecessary mutability
Manual indexing into arrays2
Lifetime annotations
Cryptic variable names
Imperative coding style
Let’s be clear: there are many, many antipatterns in the above code,<br>but the most important observation is that these antipatterns have nothing to do with Rust itself.<br>They are bad coding practices in general.
The learner has not yet fully embraced the ergonomics Rust provides and might be skeptical about performance...