My Rusty Christmas: How Rust earned a place next to C++, Java, Python and Go (and made me argue with rust traits)

Crowds of fun-seekers exploring a city on foot, "

Arjan Franzen

6 January 2026

image (1).webp

Every few years, a new language earns a permanent place in your toolkit.
Not because it’s fashionable.
Not because the internet says you should.

But because, at some point, you realise:

“This actually solves problems I already have — even if it annoys me first.”

This Christmas, Rust joined that list for me.

The starting point

My background is pretty broad:

  • ~10 years of C++ (systems, performance, RAII, undefined behaviour as lived experience)

  • A lot of Java (enterprise, scale, long-lived systems)

  • Plenty of Python (glue, speed of thought, experimentation)

  • Some Go (services, pragmatism — the least of the four)

Rust had been circling my radar for years. I admired it conceptually, but like many C++ developers, I had the usual thought:

“This looks nice, but I already know how to do this safely.”

So this Christmas, I stopped theorising and just started to actually build.

What I expected vs what happened

What I expected:

  • A steep learning curve

  • Many compiler errors

  • A lot of clever ideas wrapped in friction

What actually happened:

  • A steep learning curve

  • Many compiler errors

  • …and a growing suspicion that the compiler was usually right

A very early example:

It felt like a very opinionated code reviewer who had seen all my old mistakes before.

1let a = String::from("hello");    
2let b = a;    
3// println!("{a}"); // ❌ compile error
4

In C++, this would compile and later explode.

Rust just says “no” — immediately, and for a good reason.

Rust didn’t feel like a new paradigm.

Ownership and borrowing: fewer lies, more honesty

The big shift was ownership and borrowing.

At first it feels restrictive.

Then it doesn’t feel very pleasant.

Then — slowly — it feels honest.

1fn len(s: &String) -> usize {
2    s.len()
3}
4let s = String::from("hello");
5let n = len(&s);
6println!("{s}"); // ✅ still valid

No more casual aliasing.

No more “this reference should be fine”.

No more mental bookkeeping about who frees what and when.

Coming from C++, this didn’t feel like losing power — it felt like formalising discipline I already tried to apply manually.

Error handling without drama

Rust’s error model clicked faster than expected.

  • Errors are data

  • Failure is explicit

  • Control flow is visible

1fn read_config() -> Result<String, std::io::Error> {
2    let content = std::fs::read_to_string("config.toml")?;
3    Ok(content)
4}

The ? operator alone deserves a small shrine.

It reads exactly like what it does:

“If this fails, stop here — cleanly.”

After years of C++ exceptions and Go’s if err != nil, this felt like the grown-up version of both.

And then… numeric traits happened

At some point during Christmas, I made the classic mistake.

I wanted to write a simple function that “just works for numbers”.

Python brain said:

1def add(a, b):
2    return a + b

Rust replied:

1fn add<T>(a: T, b: T) -> T {
2    a + b
3}
4// ❌ nope

So I specified what Rust wanted:

1use std::ops::Add;
2fn add<T: Add<Output = T>>(a: T, b: T) -> T {
3    a + b
4}

Then I wanted to mix types.

1add(1i32, 2.0f64); // ❌ still nope
2

At some point, I found myself staring at trait bounds thinking:

“I have written template metaprogramming in C++.

Why is this harder than I expected?”

This is where that Rust numeric meme hits painfully close to home.

If you want “numbers” in Rust, you don’t get a free abstraction.

You get:

  • traits

  • bounds

  • associated types

  • crates like num

And the creeping realisation that this is deliberate.

The uncomfortable realisation

Then it clicked.

Rust isn’t bad at numeric abstraction.

It’s refusing to lie.

Python says:

“I’ll figure it out at runtime.”

Rust says:

“Tell me exactly what you mean, or don’t ship.”

So you end up writing:

1fn add_as_f64<A: Into<f64>, B: Into<f64>>(a: A, b: B) -> f64 {
2    a.into() + b.into()
3}

Yes, that’s more explicit.

Yes, that’s more friction.

But it also means:

  • no silent truncation

  • no implicit widening

  • no platform-dependent behaviour

  • no “this worked in tests but not in production” math bugs

I didn’t enjoy that realisation in the moment.

But I respected it.

Undefined Behaviour (UB), now with fences

After enough years in C++, you don’t fear crashes.

You fear the bugs that only show up in production.

Rust’s stance on UB is refreshingly blunt:

1unsafe {
2    let p = 0xdeadbeef as *const i32;
3    println!("{}", *p); // UB lives here, clearly marked
4}

Yes, UB exists.

No, you don’t get it accidentally.

If you want it, you must opt in explicitly.

That changes how you think about boundaries — and about trust.

Async without a GC (still slightly magical)

Async Rust deserves its own article, but briefly:

1async fn fetch(url: &str) -> Result<String, reqwest::Error> {
2    let body = reqwest::get(url).await?.text().await?;
3    Ok(body)
4}
  • async/await without a garbage collector

  • Futures as explicit state machines

  • No runtime hiding memory costs

Yes, the borrow checker gets louder here.

Yes, you sometimes have to restructure code.

But the result is predictable performance with compile-time safety, which is still rare.

From Christmas experiment to actual work

The real signal wasn’t intellectual.

This didn’t stay a holiday experiment.

Before Christmas was over, I was already using Rust in a ZEN Software project — not as a rewrite for fun, but where Rust’s strengths actually fit:

  • correctness

  • performance

  • long-term maintainability

That’s usually the moment a language stops being interesting and starts being useful.

Where Rust sits in my toolbox now

I don’t think in terms of replacement.

For me, it now looks like this:

  1. Java→ stability, scale, predictability

  2. Python→ speed of thought, experimentation

  3. Rust → correctness under constraints, without giving up performance

  4. Go→ pragmatic services

  5. C++ → ultimate control, legacy ecosystems, sharp edges

Rust didn’t make me feel less experienced. It made me feel like my experience finally had a compiler that backed me up — even when it annoyed me.

Closing thoughts

I didn’t “switch” to Rust this Christmas.

I added it.

Yes, I argued with traits.

Yes, I cursed numeric bounds.

Yes, I lost a few hours to the borrow checker.

But the result stuck.

2026 is going to be a bit more Rusty.

And I’m genuinely looking forward to it. 🦀

image (2).webp
background

Optimize with ZEN's Expertise