Jump to content

The Rust Odyssey: Months 3–7 aka The Monk Mode Chronicles

From JOHNWICK

In which our hero discovers that Rust is perfectly ordinary, deeply tedious, and somehow that’s the most deflating revelation of all

“Everything was beautiful and nothing hurt.”
— Kurt Vonnegut, Slaughterhouse-Five —
(Which is absolutely not how these five months felt, but one can dream)


Five months of silence (so now it must be a bit longer, sorry and “brace yourselves”). Not because I quit — though the thought crossed my mind with the regularity of production incidents during a “routine” deployment. Not because I achieved enlightenment — though Month 2’s desperate SOS might have suggested some dramatic transformation was imminent.

No, I stopped writing because the story I expected to tell — epic battles with the borrow checker, heroic breakthroughs, triumph over adversity — never materialised. What happened instead was considerably more mundane and, somehow, more deflating: I just… kept going. Evening after evening. Page after page. Error message after error message.

As it turns out, learning is less hero’s journey and more Sisyphean bureaucracy with better tooling. Month 2 ended with me exhausted, frustrated, stuck on a plateau that stretched to the horizon like a particularly unforgiving Eastern European highway. I called out for help, for war stories, for reassurance that this phase would pass. Some kind souls responded (cheers, mates). Others nodded knowingly and moved on. Most people, quite sensibly, were too busy writing actual code to comment on someone else’s melodramatic struggle with a programming language.

I promised updates. I promised documentation of the journey. I promised to chronicle the transformation from confused novice to… something resembling competence.

Instead, I disappeared into what can only be described as monk mode — though monks presumably have better work-life balance and fewer Slack notifications.

So here we are, five months later, emerging from radio silence not with tales of glory, but with something considerably less inspiring: a retrospective on why learning things is boring, why Rust is simultaneously the best and most irrelevant language I’ve learned (sort of), and why the real enemy was never the borrow checker.

Spoiler alert: I’m still mediocre (mildly speaking). Still feel stupid. But the language turned out to be… easy.

And that, somehow, is the worst part.


I. The Monk Mode Rationalisation (Or: High-Minded Excuses for Going Dark)

…omm… “The ships hung in the sky in much the same way that bricks don’t.”
— Douglas Adams, The Hitchhiker’s Guide to the Galaxy —
(Much like my understanding of Rust: technically present, defying normal expectations) Let me construct the philosophical justification I told myself during these silent months. It goes something like this…

Writing about learning interferes with actual learning. The act of meta-cognition — thinking about thinking, documenting the process, crafting narratives about one’s struggle — creates distance from the material itself. Better to simply do the work. Better to embrace the Taoist principle of wei wu wei — action through non-action. Or in this case, learning through non-blogging.

The Stoics would approve. Marcus Aurelius didn’t live-tweet his meditations. Epictetus didn’t maintain a Substack about Stoic practice. They simply practised. Quietly. With discipline. Without the performative documentation that modern self-improvement culture demands.

Zen practitioners speak of “beginner’s mind” — approaching each moment with fresh awareness, unclouded by preconception or narrative. How can one maintain beginner’s mind whilst simultaneously crafting meta-narratives about maintaining beginner’s mind (inception warning)? The act of observation changes the observed. Schrödinger’s learning curve, if you will. This sounds quite sophisticated, doesn’t it? Almost convincing. The reality, of course, was considerably less enlightened.


The Actual Situation

To be fair, the silence wasn’t entirely self-imposed philosophical retreat. Work had erupted into one of those special periods that management likes to call “all hands on deck” and engineers privately refer to as “the interesting times” (curse included, warranty voided).

…an ordinary day in extraordinary mess… The regular fireworks created what one might charitably describe as an “inspiring atmosphere of creative chaos” and what one might honestly describe as “daily questioning of life choices.” Picture a symphony orchestra where half the musicians are reading different sheet music, the conductor is on holiday, and someone’s replaced the fire extinguishers with confetti cannons. Now add distributed systems, legacy code, and deadlines that were ambitious when they were set six months ago.

“It is a mistake to think you can solve any major problems just with potatoes.”
— Douglas Adams —
(Replace “potatoes” with “TypeScript” and you have my work situation) In this context, Rust became something unexpected: not the additional burden I’d anticipated, but a refuge. An evening escape where at least the chaos was predictable. Where at least the compiler told you exactly what was wrong instead of waiting for the issue to surface three weeks later in production at 3 AM. Work was unpredictable chaos — shifting requirements, legacy systems held together with optimism and technical debt, emergencies that somehow always arrived right before weekend plans. Rust was predictable chaos — error messages with line numbers, problems with mechanical solutions, a type system that didn’t care about your feelings but at least was consistent about it.

…my tiny solitude… If I was going to feel exhausted and stupid, I’d rather feel that way about something that could be solved mechanically — taking a shovel and moving the mud, as it were — rather than something that required navigating organisational politics and explaining to stakeholders why the “simple change” they requested would require rewriting three years of accumulated architectural decisions.

Programming Rust by Blandy, Orendorff, and Tindall became my evening companion — the thorough, comprehensive tome that goes deeper than “The Book” in areas I actually needed. When that felt too dense, I’d flip to Drysdale’s Effective Rust for more focused, practical guidance on patterns and idioms. The routine was simple: read a section, try the concepts, fail, read again, try again, fail better. Rustlings exercises flowed past in a meditative loop. The same patterns, repeated until muscle memory replaced conscious thought. Option and Result. & and &mut. impl Trait and dyn Trait. The concepts settled into neural pathways not through dramatic breakthrough but through sheer repetitive exposure.

The calisthenics programme I’d mentioned in Month 1? Continues, actually — three to four times weekly, supplemented with gym sessions. The German studies? Ongoing, for entertainment purposes. Assuming, of course, that German grammar qualifies as entertainment, which is debatable. The paleo diet? Well, let’s just say that deadline stress and dietary discipline don’t coexist peacefully, and that particular experiment has been… adapted.

What remained was this: code, books, bodyweight exercises, German declensions, and silence. A curious collection of deliberate difficulties, each feeding into the others in ways I don’t entirely understand. “We suffer more often in imagination than in reality.”
— Seneca —
(Though Rust’s borrow checker ensures suffering in both equally)

Perhaps there’s wisdom in this distribution of pain. Perhaps the physical exhaustion makes the mental exhaustion more bearable. Perhaps learning one difficult thing makes learning another feel more manageable. Perhaps I’m simply a masochist with a tendency to intellectualise my poor life choices. Or perhaps I’m just trying to make “I got busy and stopped documenting” sound more profound than it actually was.


The Absence of Drama The real problem with these five months — the reason they went undocumented — is that nothing dramatic happened.

No breakthrough moment where everything suddenly clicked. No epic battle with the borrow checker that ended in triumph. No dark night of the soul followed by redemptive understanding.

…word by word by word by… Just… gradual, boring competence. It’s like learning to touch-type. For weeks, you’re painfully slow, looking at the keyboard, making mistakes. Then one day you realise you’ve been typing for an hour without looking down. There was no moment of transformation. Just accumulation of muscle memory until the skill became automatic. Rust was the same. Ownership made sense. Then borrowing made sense. Then lifetimes made sense. Not all at once. Not with fireworks. Just… eventually. Through repetition. Through writing code that didn’t compile until it did. Through reading error messages until they stopped feeling like criticism and started feeling like guidance.

“We tell ourselves learning is a hero’s journey. It’s not. It’s bureaucracy. It’s filling out forms until you know where all the fields are. It’s repetition until pattern recognition automates the thinking.”
— Anonymous Codecrafter

The borrow checker didn’t become my friend. I just developed Stockholm syndrome with better error messages.

And that makes for terrible narrative. Nobody wants to read: “Day 47: Compiled successfully on the first try. Day 48: Also compiled successfully. Day 49: Spent two hours debugging but it was my logic, not the ownership system.”

Where’s the drama? Where’s the conflict? Where’s the narrative arc? There isn’t one. There’s just practice. And practice, whilst effective, is spectacularly boring to document.


II. The Inconvenient Truths (Or: The Projects I Definitely Didn’t Write for Production)

…smuggling The Crab… “The difference between fiction and reality? Fiction has to make sense.”
— Tom Clancy —
(attributed, disputed — either way, accurate about my production code) About those projects I swore I wouldn’t write…

In my defence — and I’m aware how hollow this sounds — they were supposed to be mere proofs of concept. Exploratory code. The programming equivalent of doodling in margins. Somehow, these doodles ended up in production.

As the saying goes: “In theory, theory and practice are the same. In practice, they aren’t.” In theory, I was learning. In practice, I was shipping. The boundary got… blurry.

Let me be clear about what happened, because the guilt has been accumulating like technical debt, and confession is supposedly good for the soul (though I suspect it’s better for the confessor’s reputation management than actual soul maintenance).


The Solemn Vow (And Its Violation)

Month 0, that optimistic declaration on April 1st, included a promise: no production code. This was to be a year of pure learning. Of fundamentals. Of building understanding without the pressure of delivery. Noble intentions, really.

The reasoning was sound: learning under deadline pressure produces shallow knowledge. You learn enough to solve the immediate problem, then move on, never building deep understanding. Better to study deliberately, systematically, without the contamination of real-world constraints. This worked for approximately three months.

Then reality, in its infinite creativity for undermining plans, intervened.


Project One: The Custom Image Loader

Context: Next.js application. Client’s infrastructure. Not Vercel. Image optimisation needed. Standard solutions weren’t fitting the constraints. Someone (me) mentioned, casually, that Rust has excellent image processing libraries.

…holly crab… Someone else (also me, in a moment of weakness) said: “I could probably build a quick proof of concept…” “Any sufficiently advanced technology is indistinguishable from magic.”
— Arthur C. Clarke’s Third Law

The proof of concept worked. Of course it did. Because Rust, despite my ongoing sense of inadequacy, is actually quite good at this sort of thing. The image crate handles format conversion elegantly. The performance was (is) excellent. The error handling was explicit enough that deployment confidence came easier than expected.

Defence: “It was just a PoC…”
Reality: It shipped.

Further defence: “Technically not my production code, it’s the company’s…”
Counter-reality: I still wrote Rust in production, breaking my vow with the casualness of someone discovering that rules are more like guidelines.

The cognitive dissonance is exquisite. I’m simultaneously proud that I could write working Rust code under a deadline, and ashamed that I compromised my pure learning year with filthy pragmatism. The Stoics would probably have something wise to say about this. I’m too busy pretending this project doesn’t count to figure out what.


Project Two: The Scraper and Parser

Context: Legacy site migration. Thousands of articles. HTML structure from an era when “semantic markup” meant “it displays something”, but someone decided to build (sort of) layout builder out of it. Manual migration? Insanity. Automated extraction? Necessary.

…put it here put that there… err. scrape the planet… Someone (me again, noticing a pattern here?) suggested Rust might handle this well. Defence: “One-off script, doesn’t count as production…”
Reality: Mission-critical data migration tool that ran for descent amount of time straight. Defence Two: “Nobody uses it anymore…” 
Reality Two: Without it, the migration would have taken months instead of days. But here’s the thing that actually surprised me — the part that justified this whole digression into my moral compromises:

Pattern matching in Rust is genuinely, surprisingly, almost offensively better than Python for certain parsing tasks.

This came as a shock. Python’s been my data munging tool of choice for years. Beautiful Soup, regex, list comprehensions — I could parse HTML in my sleep, usually whilst actively debugging why Beautiful Soup was being less than beautiful about malformed tags. But Rust’s match expressions combined with its enum system? “It’s still magic even if you know how it’s done.”
— Terry Pratchett, A Hat Full of Sky

It’s like discovering your reliable Honda Civic has been hiding a racing mode all along. The explicitness that makes Rust verbose elsewhere becomes an asset when you’re tearing apart legacy HTML looking for patterns. No silent None propagation. No surprise exceptions. Just: does this match the pattern? Yes or no. Handle both cases. Move on.

The pattern matching made complex parsing logic readable. Not just functional — readable. And I believe it is what actually matters: screw up the speed and memory safety cult in this case. Future me (or any other poor soul maintaining this) could look at the match arms and understand the decision tree immediately. Python’s if-elif chains or Beautiful Soup’s .find() chains never achieved that clarity.

The scraper worked. First time. Well, second time. Actually it required lot of manual tweaking, but the parts that went through were rather solid laying out descent foundations for the next steps. Okay, after several hours of fighting the borrow checker over whether I was allowed to iterate over elements whilst also storing references to parent nodes. But then it worked consistently, which in the world of web scraping feels like actual sorcery.

Three months prior, this would have taken me days, possibly weeks. Now? An afternoon. Maybe two, three: if I’m being honest and counting the time spent on muddlint through the net figuring out why the scraper crate's selectors work slightly differently than I expected.


The Verdict on Rule-Breaking

So yes, I broke my own rules. I wrote production Rust during my “pure learning year.” I shipped code that people depended on. I let pragmatism override principle. And you know what? I feel… fine about it.

…i am what i am… Not entirely fine — there’s guilt, definitely. The kind that comes from knowing you’ve compromised your ideals. But also relief. Because the code worked. Because deadlines were met. Because the theoretical purity of “learning for learning’s sake” crashed into the reality of “this problem needs solving and you have the tools.” “The impediment to action advances action. What stands in the way becomes the way.”
— Marcus Aurelius, Meditations

The Stoics speak of accepting what is. What was: I learned Rust well enough to solve real problems under real constraints. What is: that’s probably the best validation of learning possible.

But these are diversions — anecdotes rather than the main narrative. War stories to share over drinks with other developers who’ve also discovered that plans and reality rarely align perfectly.

The real story of these silent months isn’t in the code that shipped, but in the code that didn’t need to. In the evening practice that built competence without fanfare. In the slow, boring accumulation of understanding that has no dramatic beats to mark its progress.

So let’s return to that story. The one without heroes or villains. Just a programmer, a language, and five months of showing up even when there was nothing exciting to report.


III. The Unbearable Ordinariness of Rust

…repetetive and boooooring… “Man has gone out to explore other worlds and other civilizations without having explored his own labyrinth of dark passages and secret chambers.”
— Stanisław Lem, Solaris

(Swap “dark passages” with “lifetime annotations” and you have my Month 4) After five months of monk mode, methodical reading, evening kata, and occasional cheating with “PoC” production code, I’ve arrived at the most deflating conclusion possible: Rust is easy.

Not “easy” as in trivial. Not “easy” as in you’ll master it in a weekend. But easy as in: logical, consistent, predictable. The rules make sense. The compiler is clear. The patterns are learnable. There’s no magic. No mystery. No secret handshake required.

It’s just… a programming language. A fussy, verbose, safety-obsessed programming language that refuses to let you do stupid things. But ultimately, boringly, deflatingly: ordinary. This feels rather like what I imagine discovering your entire mythology is based on a clerical error would feel like. Disappointing? Yes. Humbling? Absolutely. Character-building? Probably, though I’d have preferred the character to be built through something more impressive than discovering I’m just slow at pattern recognition.

As Vonnegut might say: “And so it goes.” The borrow checker wasn’t a dragon. It was a particularly pedantic librarian with clear rules that I simply refused to read carefully.


The Predictable Predictability

Here’s what I’ve learned about Rust after five months of consistent exposure: when something doesn’t work, you know it’s your fault. Not in the philosophical “we are all responsible for our own learning” sense. In the much more practical “the compiler told you exactly what was wrong, with error code and suggestion, and you ignored it because you thought you knew better” sense.

Debugging in Rust isn’t mysterious. It’s tedious. There’s a difference.

In JavaScript, when something breaks, you engage in detective work. console.log() archaeology. Stack trace interpretation. The subtle art of figuring out whether undefined means "I forgot to initialise this" or "the API changed" or "timing issue" or "honestly who knows, try refreshing."

In Python, exceptions give you clues, but those clues often involve stack traces through library code you’ve never seen, leading to StackOverflow threads from 2015 that may or may not still be relevant. In Go, if err != nil tells you something went wrong, but figuring out why often involves adding more logging and retracing your steps.

In Rust, the compiler tells you: “This won’t work because you’re trying to use data after you moved it on line 47. Here's the exact location. Here's what you probably meant to do. Here's the documentation link if you need more context."

It’s not mysterious. It’s mechanical. You take the shovel, you move the mud.

// This doesn't work. You know it won't work before compiling.
fn broken_example() {
    let data = vec![1, 2, 3];
    let first = data.get(0);
    let second = data.get(1);
    process(data); // moved value
    println!("{:?}", first); // use of moved value: `data`
}

// The compiler tells you exactly this. With line numbers.
// With suggestions. With links to documentation.
You fix it mechanically:
fn working_example() {
    let data = vec![1, 2, 3];
    let first = data.get(0).copied();
    let second = data.get(1).copied();
    process(data.clone()); // or pass by reference, or restructure
    println!("{:?}", first);
}

No mystery. No magic. Just: read what the compiler said, do what it suggested, code compiles. The pattern becomes so routine it stops being a battle and becomes… clerical work. Is this empowering? In a way. You know exactly where you stand. The feedback loop is tight. The rules are clear. Is this exciting? Not even slightly.


The Byzantine Syntax Becomes Boring

…thou shal this thou shall that thou shalt be a twat… “The bureaucracy is expanding to meet the needs of the expanding bureaucracy.”
— Attributed to Oscar Wilde (disputed), but spiritually accurate for Rust syntax In Month 2, Rust’s syntax felt like an assault. Turbofish operators. Lifetime annotations. Trait bounds. impl Trait versus dyn Trait. The ampersands and asterisks scattered through code like landmines for the conceptually unwary.

After five months? It’s… tedious. Not in the “unbearably difficult” way. In the “oh for fuck’s sake, another lifetime annotation” way. Like Go’s if err != nil—annoying, verbose, but you understand why it's there and you just... do it.

// Month 2: "WHAT THE HELL IS THIS UNHOLY SYNTAX"
fn complex<'a, T>(item: &'a T) -> &'a T 
where 
    T: Clone + Debug + Display 
{
    item
}

// Month 7: *sigh* "Yeah, lifetime 'a, trait bounds, whatever"
// It's not that it became easy. It became routine.
// Which is worse, somehow.

The syntax stopped being scary and started being annoying in the way that paperwork is annoying. Necessary. Serving a purpose. But nobody wakes up excited about filing expense reports, and nobody should pretend that writing impl Iterator<Item = Result<T, E>> is the highlight of their day. It’s like if err != nil in Go, but with more types and more explicitness. You understand the reasoning—safety, clarity, compiler optimisation. You appreciate the goals. You still resent typing it. The difference is that in Month 2, this resentment came packaged with confusion and frustration. In Month 7, it comes packaged with resigned acceptance. Progress, I suppose. Of the most mundane kind.


Concurrency: Still Teeth-Grinding

…come back you little #$$^$%^$^… “I’m sorry, Dave. I’m afraid I can’t do that.”
— HAL 9000, 2001: A Space Odyssey —
(The borrow checker, every time I try concurrent mutation) If there’s one area where Rust remains genuinely, consistently painful, it’s concurrency. Not because it’s mysterious — it’s perfectly logical once you understand Send, Sync, Arc, Mutex, and why you can’t just share mutable state like some kind of garbage-collected barbarian. But because the ceremony required to do anything concurrent feels like filling out forms in triplicate for the privilege of running two functions at the same time.

// Concurrency still makes me want to cry
use std::sync::{Arc, Mutex};
use std::thread;

fn concurrent_pain() {
    let data = Arc::new(Mutex::new(vec![]));
    let mut handles = vec![];
    
    for i in 0..10 {
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let mut d = data_clone.lock().unwrap();
            d.push(i);
        });
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
}
// Do I love this? No.
// Do I understand why it's necessary? Unfortunately, yes.
// Has it become easier? Marginally.
// Is it still tedious and verbose? Absolutely.
Compare this to Go:
func concurrent_simplicity() {
    var data []int
    var mu sync.Mutex
    var wg sync.WaitGroup
    
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            mu.Lock()
            data = append(data, i)
            mu.Unlock()
        }(i)
    }
    wg.Wait()
}

Is Go’s version safer? Debatable. Is it simpler to write? Undeniably. Does Rust’s version prevent entire categories of bugs? Yes. Do I still resent writing it? Also yes. The complexity is justified. The safety guarantees are real. The prevention of data races is valuable. But riding an ergonomically-optimised bicycle is still more pleasant than riding a wheel with sharp edges. Both get you there. One is considerably more comfortable (was I using sharp wheel? I bet you know the answer).

Rust’s concurrency is the sharp-edged wheel. Functional. Reliable. Predictable in its discomfort. You know it won’t suddenly collapse. You also know your arse is going to hurt afterwards.


Why This Feels WORSE

The uncomfortable truth: If Rust isn’t hard — if it’s just tedious, mechanical, predictable — then why was I finding it hard? The imposter syndrome that briefly retreated in Month 1 returns with reinforcements. The Rust community’s war stories about “fighting the borrow checker” now feel like humble bragging. “Oh yes, lifetimes were SO difficult” — were they though? Or was I just slow?

Maybe the plateau in Month 2 wasn’t Rust’s difficulty. Maybe it was just… me. Learning at normal human speed. Expecting mastery to arrive faster than it actually does. Wanting the competence without the boring accumulation of practice.

…maybe it was me all the time… The uncomfortable truth arrives with all the gentleness of a 3 AM realisation that you’ve been doing something wrong for years: maybe I’m not learning a difficult language. Maybe I’m just… learning. At normal human speed. Without the consolation of claiming the mountain was particularly tall.

“The saddest aspect of life right now is that science gathers knowledge faster than society gathers wisdom.”
— Isaac Asimov Replace “science” with “Rust compilers” and “society” with “my brain” and you have the last five months in a sentence.

The Stoics speak of obstacles as the way forward. But what if the obstacle was never the language? What if the obstacle was my expectation that this should feel like mastery sooner? The borrow checker was never the enemy. My ego was. My impatience was. My desire for quick wins was. Rust was just there. Being Rust. Being perfectly reasonable whilst I flailed around it like an undergraduate discovering for the first time that things require actual effort.


Rust as Refuge Here’s something I didn’t expect: Rust as evening refuge rather than evening torture.

…just chill and let it flow… Work remains chaos — the “inspiring disarray” of distributed systems, legacy code, shifting priorities, and what project managers optimistically call “emerging requirements” and engineers pessimistically call “we didn’t think this through the first time.”

The problems at work are often unsolvable, or at least unsolvable within current constraints. The codebase has accumulated fifteen years of decisions made by people who are no longer around to explain them. The bugs are intermittent, environment-specific, and occasionally quantum in nature (observing them changes their behaviour).

Rust problems? Solvable. Mechanical. The compiler tells you what’s wrong. You fix it. The fix works. Reliably. Repeatably. There’s comfort in that predictability.

When work feels like navigating a maze in the dark whilst someone keeps moving the walls, spending an evening where the rules stay consistent and the feedback is immediate becomes oddly soothing. Even if that feedback is “no, you can’t do that, here’s why, try this instead.”

It’s the programming equivalent of doing a crossword puzzle after a day of interpersonal conflict. The problems have answers. The answers work consistently. Nobody’s going to change the specification halfway through because a stakeholder just had a “brilliant idea.”

Was this the intention? Learning Rust as stress management rather than career advancement? Not consciously. But the subconscious is wiser than the ego, and apparently mine decided that what I needed wasn’t mastery but reliability.

Even if that reliability came wrapped in angle brackets and lifetime annotations.


IV. The Neurotic Aftermath (Or: How Rust Infected Everything Else)

…release inner inquisitor… “We are stuck with technology when what we really want is just stuff that works.”
— Douglas Adams —
(Turns out Rust is the “stuff that works” but at the cost of making everything else feel broken) Learning Rust has side effects. Not the kind listed on pharmaceutical warning labels, but side effects nonetheless. The infection spreads. The neuroses multiply. The way you see other languages… changes. It’s like learning about germs. You can’t unsee them. Suddenly, every doorknob is suspect. Every handshake is a potential vector for disease. Every buffet is a microbial horror show waiting to happen. Except with code. And instead of germs, it’s: unhandled nulls, unchecked mutations, race conditions hiding in plain sight, and the casual violence that garbage-collected languages inflict upon the concept of ownership.


The TypeScript Infection

Before Rust, I wrote TypeScript with the confidence of someone who’d convinced themselves that types were enough. That if the type checker passed, the code was probably fine. That null and undefined were problems, sure, but manageable problems. After Rust? Every unhandled null is a personal affront. Every mutation feels like a crime scene waiting to happen. Every ! (non-null assertion) operator looks like a loaded gun with the safety off.

// Month 0: Totally fine with this
function processUser(user: User) {
    user.name = user.name.toUpperCase();
    user.lastUpdated = new Date();
    return user;
}

// No second thoughts. It works. Ship it.
// Month 7: *eye twitches*
function processUser(user: User): User {
    // Why are we mutating? Who else has a reference?
    // What if this is called concurrently?
    // RUST HAS RUINED ME
    return {
        ...user,
        name: user.name.toUpperCase(),
        lastUpdated: new Date(),
    };
}

// Better? Probably. More annoying to write? Definitely.
// Do my teammates notice? They've stopped asking.
The infection is real. I catch myself writing more functional code. More defensive code. Code that assumes everything can fail and handles it explicitly. Code that treats mutations like nuclear waste — dangerous, necessary sometimes, but requiring explicit containment protocols.
// The new paranoia
function fetchUserData(userId: string): Promise<User | null> {
    // What if userId is empty? What if the API is down?
    // What if the response is malformed? What if...
    if (!userId) return Promise.resolve(null);
    
    return fetch(`/api/users/${userId}`)
        .then(res => res.ok ? res.json() : null)
        .catch(() => null);
}

// Everything returns null. Everything handles null.
// Everything is explicit. Everything is tedious.
// Everything is... safer? Maybe?


The Python Mutation Horror Python, beautiful, permissive Python, where everything is mutable and nobody cares because the GIL protects you from your own worst instincts (mostly). Before Rust: blissful ignorance.
After Rust: constant low-level anxiety.

# This now makes me uncomfortable
def update_config(config):
    config['timeout'] = 1000  # MUTATION! SIDE EFFECTS! AAAH!
    config['retries'] = 3
    return config

# I catch myself writing this instead
def update_config(config):
    return {**config, 'timeout': 1000, 'retries': 3}
# My Python colleagues: "Why are you like this?"
# Me: "I wish I knew."

The data science team looks at me with concern. “Why are you creating new dictionaries? Just update the existing one. It’s fine.” And they’re right. It is fine. Python’s been fine for decades. But my brain, now infected with Rust’s ownership paranoia, sees mutations everywhere and flinches.


Go: A New Appreciation

Interestingly, Rust hasn’t made me resent Go. If anything, it’s deepened my appreciation for Go’s particular brand of simplicity.

…hello gopher my old friend… Go doesn’t pretend to be safe. It doesn’t pretend to prevent all errors. It just says: “Here’s if err != nil. Handle your errors explicitly. Don't do stupid things. Good luck." There’s honesty in that approach. A clarity. After Rust’s elaborate type system and ownership rules, Go feels like someone saying “just be careful” instead of installing guardrails on every staircase.

// Go's honesty is refreshing
data, err := fetchData()
if err != nil {
    log.Printf("failed to fetch: %v", err)
    return err
}

// No ceremony. No trait bounds. No lifetimes.
// Just: did it work? No? Handle it. Move on.

Is this safer than Rust? No. Is it simpler? Yes. Is simplicity worth the tradeoff? Depends on what you’re building. But after months of Rust’s safety-at-all-costs philosophy, Go’s “be an adult about it” approach feels almost liberating. Like taking off a helmet after a long bike ride. You remember why helmets exist. You also remember why sometimes you just want to feel the wind.


The Inevitable Compromise Here’s the thing about neuroses: you can develop them, but you can’t maintain them indefinitely. Eventually, pragmatism wins.

Yes, I’m now hyper-aware of mutations in TypeScript. Do I still mutate objects when deadlines loom? Absolutely.

Yes, I understand the value of immutability in Python. Do I create new dictionaries for every update in production data pipelines? Not when memory is limited and performance matters. Yes, Go’s error handling could be more sophisticated. Do I still write Go for services that need to be simple, maintainable, and shipped yesterday? Every time. The infection spread. I became more aware. More careful. More neurotic. And then, inevitably, I learned to ignore my own neuroses and ship anyway. “Strange women lying in ponds distributing swords is no basis for a system of government.”
— Monty Python, Monty Python and the Holy Grail —
(Strange compilers distributing type systems is no basis for software architecture) Perhaps this is wisdom. Perhaps it’s defeat. I genuinely can’t tell anymore. But I suspect it’s actually maturity — understanding the ideals whilst accepting the constraints. Writing better code when you can. Writing working code when you must.

Rust changed how I think. It didn’t change what I ship. And maybe that’s okay.


V. The Pragmatist’s Dilemma (Or: Why I’m Still Not Rewriting Everything In Rust)

…at the end of the day there is a job to be shipped… “In theory, there is no difference between theory and practice. In practice, there is.”
— Yogi Berra (disputed), or Jan L.A. van de Snepscheut (actual), or every programmer ever (lived experience) Here’s where I’m supposed to have my conversion moment. Where the Rust evangelist emerges, butterfly-like, ready to proclaim the good news of memory safety and fearless concurrency to all who will listen. Except… that didn’t happen.

Don’t misunderstand — Rust is magnificent. Objectively the best-designed systems language available. The type system is exquisite. The ownership model is intellectually satisfying. The safety guarantees are real. Indisputably, as one might say if one wanted to emphasise the inarguable nature of this assessment.

But. (There’s always a ‘but’, isn’t there?) As Voltaire nearly said: “Perfect is the enemy of good enough to ship by Friday.”


The Cloud Zoo of Distributed Chaos

Work remains a beautiful disaster of microservices, container orchestration, message queues, and what can only be described as “infrastructure herding of mules” — the technical term is “service mesh”, but I prefer the more honest descriptor.

For this? Go wins. Every time. Not because it’s better — Rust would be better. But because Go is good enough, boring enough, and gets out of your way enough that you can focus on the actual distributed systems problems rather than negotiating with a type system about whether your HTTP client should live for 'static or 'a.

Go does its job. It compiles fast, deploys fast, crashes predictably, and when it does, you know exactly why because the error handling is so tediously explicit that even at 3 AM during an incident, you can trace the problem.

// Go: boring, works, ships
func handleRequest(w http.ResponseWriter, r *http.Request) {
    data, err := fetchFromService(r.Context())
    if err != nil {
        log.Printf("error: %v", err)
        http.Error(w, "service unavailable", 503)
        return
    }
    json.NewEncoder(w).Encode(data)
}

// 20 lines, understood by everyone on the team,
// deployed to production, handling 10k req/s.
// Perfect? No. Shipped? Yes.
Compare this to what introducing Rust to the stack would mean:
// Rust: beautiful, correct, still in PR review discussing whether
// we should use Arc<RwLock<T>> or tokio::sync::RwLock
async fn handle_request(req: Request) -> Result<Response, BoxedError> {
    let data = fetch_from_service(req).await?;
    Ok(Response::json(&data)?)
}

// More elegant? Absolutely.
// In production? Still discussing the error handling strategy.
// Team velocity? Blocked on "learning Rust" story points.
// Timeline for shipping? See you next quarter.

Is it elegant? No.
Is it safe? Debatable.
Does it let you ship functioning distributed systems? Absolutely. And in the cloud zoo of orchestration and mule-herding, functioning systems beat perfect architecture that’s still being debated six sprints later.


Python: The Wire-Thinkers

Data scientists, analysts, the thinkers-made-of-wire who transform business questions into SQL nightmares and pandas gymnastics: they live in Python. Not because it’s perfect, but because it’s there, has libraries for everything, and Jupyter notebooks are how you show work to stakeholders who don’t read code. Could I rewrite the data pipeline in Rust? Sure. Polars exists. DataFusion exists. The performance would be better. The safety would be better. The everything would be better. Should I? Absolutely not.

The data team already knows Python. The libraries exist. The ecosystem is mature. The websearch answers are plentiful. The hiring pool is deep.

The scraper I wrote proved Rust can do it — but that was a solo mission. Team work requires team languages. And the team language for “turn this business disaster into insights” remains Python.

# The data science reality
import pandas as pd
import numpy as np

def analyze_user_behavior(data):
    # Could this be faster in Rust? Yes.
    # Could this be safer in Rust? Yes.
    # Does anyone on the data team know Rust? No.
    # Will this be maintained after I leave? Only if it's Python.
    
    return (data
        .groupby('user_id')
        .agg({'events': 'count', 'revenue': 'sum'})
        .sort_values('revenue', ascending=False))

Pragmatism isn’t defeat. It’s acknowledging that the best tool is the one your team can maintain after you get hit by a bus. Or, more optimistically, the tool your team can extend when you’re on holiday and something breaks.


React: Pardon My French

And then there’s the front end. The user-facing chaos. The place where Rust doesn’t even pretend to compete outside of WASM experiments (Leptos) that always sound more promising in conference talks than in production. React. TypeScript. The build toolchain from hell. Webpack configs that read like Lovecraftian horror, newer tooling is only a bit better. npm packages with dependency trees deeper than the Mariana Trench. Do I enjoy this? No. 
Does Rust help here? Not really. (WASM enthusiasts: save your emails) 
Must it be done anyway? Yes, without enthusiasm. The front end will be JavaScript-based because that’s what browsers understand, that’s what the ecosystem provides, and that’s what the hiring pool knows. Rust can have opinions about this. Rust’s opinions will be ignored.

// The frontend reality export const UserDashboard: React.FC<Props> = ({ userId }) => {

   // Could WASM make this faster? Maybe.
   // Would it make the codebase more maintainable? Debatable.
   // Would it make hiring harder? Definitely.
   // Is the current performance actually a problem? No.
   
   const [data, setData] = useState<UserData | null>(null);
   
   useEffect(() => {
       fetchUserData(userId).then(setData);
   }, [userId]);
   

return

{/* ... */}

;

} C’est la vie... M**de!!! Or, pardon my French.


The Pragmatist’s Conclusion

…right lingo for the job… “The reasonable man adapts himself to the world: the unreasonable one persists in trying to adapt the world to himself. Therefore all progress depends on the unreasonable man.”
— George Bernard Shaw Shaw was wrong about one thing: in software, the unreasonable man who insists on the perfect tool often just ends up with nothing shipped and a lot of strongly worded GitHub issues. Rust changed how I think. It infected my code in other languages. It made me more careful, more explicit, more aware of what can go wrong. But it didn’t make me unreasonable, therefore summarizing previous mumbling:

  • For distributed cloud chaos: Go.
  • For data wrangling and the wire-thinkers: Python.
  • For frontend work: React (unfortunately).
  • For systems programming, CLI tools, performance-critical services, and evening kata that keeps me sane during work fireworks: Rust.

Each tool for its job. Each job for its tool.
Perfect is the enemy of good enough.
And good enough, as it turns out, is usually good enough. “We have normality. I repeat, we have normality. Anything you still can’t cope with is therefore your own problem.”
— Douglas Adams, The Hitchhiker’s Guide to the Galaxy


VI. The Current State (Or: Where I Actually Am in November 2025)

…;et’s think it through… “I know that I know nothing.”
— Socrates — “I know that I know slightly more than nothing, but remain unconvinced this represents progress.”
— Me, November 2025 Seven months into the year of deliberate pain. Five months since I last wrote anything. Time for an honest assessment of where I actually stand. Not where I wish I stood. Not where I think I should stand. Where I actually, truthfully, embarrassingly stand.


Skills Inventory: The Brutal Accounting What I can do:

  • Read most Rust code without panic attacks
  • Write basic async/await (with occasional Stack Overflow consultations)
  • Understand ownership/borrowing in principle (and mostly in practice)
  • Error handling with ? has become muscle memory
  • Trait bounds are comprehensible, if tedious
  • Pattern matching feels natural and I actively miss it in other languages
  • Iterators and closures are second nature
  • Can explain the difference between String and &str without crying

What still makes me want to quit:

  • Concurrency (Arc<Mutex<T>> nightmares persist)
  • Complex lifetime annotations (they work eventually, but never elegantly)
  • Macro system (black magic, avoid at all costs)
  • Unsafe (don’t touch, don’t look, back away slowly)
  • The module system (it works but why must it be so annoying?)
  • Trait objects vs generics (understand conceptually, mess up regularly)

Honest self-assessment: Am I good at Rust? No.
Am I competent? Barely.
Can I ship working code? Apparently, yes (see: the projects I swore I wouldn’t write).
Do I feel confident? Absolutely not.
Has anything fundamentally changed? …maybe?


The Books: A Qualified Success

Programming Rust by Blandy, Orendorff, and Tindall sits on my desk, dog-eared and annotated. Technically, I’ve read most of it. Realistically, some chapters received more attention than others. It’s the comprehensive reference I reach for when I need to understand why something works the way it does. Effective Rust by Drysdale has become my practical guide — the book I consult when I need to know the idiomatic way to solve a specific problem. Less about theory, more about “here’s how experienced Rust developers actually do this.”

Chapters I absorbed: ownership, borrowing, error handling, pattern matching, traits, iterators, common patterns and idioms.

Chapters I skimmed: advanced lifetimes, advanced traits, macros, unsafe, some of the more esoteric corners of the type system. Chapters I’ll need to re-read: all of the above, if I’m honest. Will I re-read them? Ask me in Month 12. Current prediction: probably not, unless forced by specific project needs.

The books did their job. They gave structure. They provided reference. They offered clear explanations that I occasionally understood on the first reading and frequently understood on the third. Are they finished? Technically. Are they complete? Let’s not get philosophical about what “complete” means.


Daily Reality: The Actual Practice

Evening kata continues. Sometimes. The frequency has dropped from “religious discipline” to “when I remember and have energy.” Work remains the usual inspiring chaos. TypeScript and Go dominate my days. Python appears for data work. React… exists, unfortunately. Rust appears in evenings. Sometimes multiple times a week. Sometimes once. Sometimes not at all, and then guilt accumulates until I open the editor again. The practice has become less structured. Less about following exercises and more about: “What small thing can I build tonight that will work?” CLI tools. File parsers. Small experiments. Nothing grand. Nothing that requires sustained focus across weeks.

…just do continue… “Busy, busy, busy.”
— Kurt Vonnegut, Cat’s Cradle That’s what we all are. Busy learning things we won’t master, writing code we’ll refactor, solving problems that will create new problems. The regular fireworks at work continue. Rust remains my evening escape — not from difficulty to ease, but from one kind of chaos to another. At least with Rust, the chaos has error messages with line numbers. The calisthenics continue — three to four sessions weekly, supplemented with gym work. The German studies progress, assuming “making sense of der/die/das” counts as progress. These parallel disciplines continue alongside the Rust practice, each one feeding into the others in ways I don’t entirely understand but seem to help maintain the overall routine. And so it goes.


The Non-Conversion: A Critical Point

Here’s what didn’t happen during these five months: I didn’t become a Rust evangelist. I learned Rust. I didn’t become a Rust developer. Those feel like different things. One is skill acquisition. The other is identity. The Crab Cultists merge the two — Rust becomes not just a language but a philosophy, a way of seeing software, a lens through which all other languages become suspect. Hope no offence taken here, crabby chaps. I learned (a bit of) Rust. I still write Go for distributed systems. Python for data. TypeScript (with React, pardon my French) for frontends.

Because pragmatism isn’t betrayal. It’s just… pragmatism. The five months taught me what Rust is good for. They also taught me — more importantly — what everything else is good for. Including when “perfect” would be the enemy of “shipped on time.” I can now recognise when Rust would be the right choice. I can also recognise when it would be the wrong choice, regardless of technical merits. And that second recognition might be more valuable than the first.


VII. The Anti-Climactic Verdict (Or: Was Any Of This Worth It?)

…not that bad in general… “The story so far: In the beginning the Universe was created. This has made a lot of people very angry and been widely regarded as a bad move.”
— Douglas Adams —
(Replace “Universe” with “Rust” and “people” with “me attempting to learn it” and you have a thesis) Seven months in. Five months silent. Time for the question that’s been lurking beneath every paragraph: was this worth it?

The uncomfortable answer: I don’t know. I’m measurably better at Rust than I was in April. I can write code that compiles and occasionally even does what I intended. I’ve infected my other codebases with neurotic safety patterns. I’ve learned that the hype around Rust’s difficulty is simultaneously accurate and overblown. But did I need five months of evening kata to learn what amounts to: “be explicit about ownership, the compiler will guide you, and concurrency is annoying everywhere”? The Stoics would say the value was in the practice itself, not the outcome. The Taoists would say the journey was the destination. I say: maybe I should have just learned Kubernetes.


What Actually Changed In my approach to Rust:

  • From terror to tedium (progress?)
  • From fighting the compiler to accepting its guidance (Stockholm syndrome)
  • From viewing safety as obstacle to viewing it as occasionally useful constraint
  • From “this is impossible” to “this is annoying but doable”

In my code in other languages:

  • More deliberate about mutations (sometimes)
  • More aware of null possibilities (often ignored due to deadlines)
  • More appreciation for garbage collection (frequently)
  • More functional patterns (when teammates allow it)
  • More defensive programming (when I remember)

In my approach to programming generally:

  • More suspicious of “X is hard” narratives
  • More patient with learning curves (debatable)
  • More aware that competence is just repeated stupidity with better error messages
  • More pragmatic about tool selection
  • More cynical about evangelism of any kind

Rust isn’t hard. Consistency is hard. Showing up every evening is hard. Fighting your brain’s desire for novelty and quick wins is hard. Accepting that mastery is boring accumulation rather than dramatic breakthrough is hard.

Rust was just… there. Being a programming language. Having rules. Being learnable. Whilst I invented elaborate psychological obstacles and philosophical frameworks to avoid admitting the real difficulty: I’m lazy, impatient, and want to already be good at things.


The Real Obstacle

The obstacle was never the borrow checker.
The obstacle was never lifetimes or trait bounds or async/await.
The obstacle was always the same thing that prevents most learning: Ego. Impatience. The desire for mastery without the tedium of practice. The expectation that hard things should feel epic rather than merely… hard. “Man has gone out to explore other worlds and other civilizations without having explored his own labyrinth of dark passages.”
— Stanisław Lem I went to explore Rust without exploring my own resistance to doing boring things consistently. The language was always just a language. The labyrinth was always just my brain, preferring drama to practice. I learned Rust well enough to solve real problems under real constraints. That’s validation. I didn’t become a Rust developer, but I became a better developer who happens to know Rust.

I learned that the difficulty of learning isn’t in the material but in showing up consistently. The borrow checker was never the challenge. My own consistency was. I learned to be sceptical of narratives — including my own. “Rust is hard” is both true and false. “Learning requires suffering” is both true and false. The truth is boring: learning requires time, repetition, and showing up even when there’s nothing dramatic to report. Was it worth five months of evening kata? Once again: I don’t know. If the answer is Yes then it is not for the reasons I expected when I started.


The Anti-Hero’s Journey As mentioned earlier I stopped writing because I expected drama. Breakthrough moments. Epic battles. A narrative arc worthy of documentation.

…no battles, no heroes, just a labour… What I got instead was: boring practice leading to boring competence. Not the story I wanted to tell.

  • Five months of silence.
  • Five months of kata.
  • Five months of discovering that the dragon was actually a reasonably well-documented programming language with consistent rules and helpful error messages. Which is, somehow, more deflating than if it had been a dragon.

I’m returning to writing because I’ve realised: that is the story. The anti-climactic story of learning without fanfare. Of competence without confidence. Of progress that feels like stagnation until you look back and realise you’ve actually moved.

The hero’s journey is a lie we tell ourselves to make practice feel meaningful. The truth is less inspiring but more honest: you show up, you do the work, you get slightly less bad at things. Repeat until death or retirement, whichever comes first. Is this inspiring? No.
Is it true? Unfortunately, yes.
Is it worth documenting? Apparently, since you’ve read this far.


To The Reader (If You’re In Your Own Month 2)

If you’re in your plateau, exhausted, wondering if you’re just slow: You’re not special. Neither am I. The language isn’t that hard. The learning is just tedious. Show up anyway. Not because it’s heroic. Because it’s the only way that works. The accumulation of boring evenings eventually becomes competence. Not mastery. Not expertise. Just… slightly less ignorance. The borrow checker isn’t your enemy. Your expectations are. The difficulty isn’t in Rust’s complexity but in your impatience with the pace of your own learning.

Lower your expectations. Show up anyway. Accept that competence is boring. This is the least inspiring motivational speech ever given. You’re welcome.


What’s Next (The Non-Commitment Commitment)

Five months remain until April 2026 — the arbitrary endpoint of my “year of deliberate pain.” Will I finish it? Probably. The practice continues, sporadic but persistent. Will I document it? Maybe. If there’s anything worth saying. If the silence doesn’t feel more honest than the words.

Will there be more dramatic breakthroughs? Doubt it. The pattern is clear now: slow accumulation, boring competence, occasional frustration, persistent mediocrity gradually transforming into slightly better mediocrity.

Will I continue the evening kata? When I remember. When work isn’t exploding. When I have energy. Which is to say: inconsistently, with guilt about the inconsistency. I’ll keep practising Rust with the enthusiasm of someone doing meal prep on Sunday: necessary, boring, occasionally rewarding, frequently skipped. I’ll write about it if there’s anything interesting to say. I’ll stay silent if there isn’t.

The year of deliberate pain continues. It’s just that the pain has transformed from acute suffering into chronic tedium. Which is, I suppose, progress of a sort. The learning continues. The competence accumulates. The feeling of stupidity persists but shifts context. Class dismissed.

…c’mon little chaps, we’ll see what tomorrow brings… “And so it goes.”
— Kurt Vonnegut


Written in a state of resigned clarity, somewhere between understanding and ignorance, after five months of silence and one evening of forced retrospection, November 2025. If you made it this far, you have more patience than I do. The journey continues. Updates if warranted. Or not. We’ll see. So it goes.

Read the full article here: https://medium.com/@krisguttenbergovitz/the-rust-odyssey-months-3-7-the-monk-mode-chronicles-d45f12668bd6