Rust, yes, but not everywhere

Rust, yes, but not everywhere

Table of Contents

Rust is a systems programming language that aims to provide memory safety, performance, and concurrency. It achieves this through a combination of features such as ownership, borrowing, and lifetimes, which allow it to enforce memory safety at compile time without needing a garbage collector. This makes Rust particularly suitable for performance-critical applications and systems programming, where manual memory management is typically required. Rust’s zero-cost abstractions promises that high-level features do not incur runtime overhead, further enhancing its performance. The language’s concurrency model is designed to be safe and efficient, making it easier to write concurrent code without the common pitfalls of data races.

Rust is gaining traction across industries for its reliability, performance, and memory safety. Companies like Microsoft, Amazon, and Google use it for cloud computing and distributed systems, while aerospace and automotive sectors are eyeing on its safety. Finance, healthcare, and gaming industries are also adopting Rust for security and efficiency. Additionally, it is being explored in blockchain, robotics, and embedded systems, though its ecosystem remains a work in progress. Its adoption is fueled by the need for safe, high-performance software.

But does Rust deliver on all its promises? To evaluate its real-world applicability, I tested it on two very different projects: Renk, a command-line tool for handling color palettes, and an ATtiny85 microcontroller project. The results? One turned into a monster… the other into a nightmare. 👾

Two Test Projects

Renk is a command-line utility for fetching and converting color palettes. I originally wrote a similar tool in Python, and I wanted to see if Rust could be a good alternative for this kind of workload, looking at projects that did a similar move for this kind of use-cases. It wasn’t a good idea…

I attempted to use Rust to program an ATtiny85, a small 8-bit AVR microcontroller. The goal was simple: blink an LED, then explore how Rust could improve safety in a constrained embedded environment. Spoiler alert: just getting the LED to blink was already a major struggle.

What Rust Does Well

Performance

Rust delivers excellent performance, close to C, thanks to its zero-cost abstractions and fine-grained memory control. However, this is a given for a systems language and should not be overhyped.

Code Reliability

Rust’s strict compiler ensures that code that compiles is often correct. The ? operator enforces proper error handling, which can reduce runtime issues. This strong safety model is reminiscent of Python or TypeScript’s strict typing but taken to an extreme. Its error handling is way better than C. It’s in the syntax, and it’s somewhat standardized. It doesn’t compete with C++ or other languages though.

fn download_sources(url: String) -> Result<String, Error> {
    let response = get(url)?.error_for_status()?;
    let response_text = response.text()?;
    Ok(response_text)
}
A Fresh Option for Low-Level Programming

Before Rust, the main choices for open-source systems programming were C, C++, and Ada. Rust brings a new paradigm with memory safety guarantees, making it an interesting addition.

Rust’s Frustrations and Limitations

Error Handling is Tedious

Unlike C++ (which offers structured exceptions), Rust forces explicit error propagation. This is safer, but often results in cluttered, boilerplate-heavy code.

pub enum ConvertError {
    DownloadError(reqwest::Error),
    ConversionError(ConverterError),
    ExportError(ExporterError),
}

// [...]

pub fn convert(source: &PaletteSource, destination: &str) -> Result<(), ConvertError> {
    let response_text = download_palette(&source.url).map_err(ConvertError::DownloadError)?;
    let converter = create_converter(source).map_err(ConvertError::ConversionError)?;
    let swatches = converter.extract_palette(&response_text).map_err(ConvertError::ConversionError)?;
    let palette = Palette {
        name: source.name.clone(),
        swatches,
    };

    let exporter = create_exporter(destination, source).map_err(ConvertError::ExportError)?;
    exporter.export_palette(&palette).map_err(ConvertError::ExportError)?;

    Ok(())
}
Complex Syntax

Rust’s syntax is a mix of different paradigms, leading to inconsistencies. While low-level languages don’t need to be “pretty,” Rust’s design choices don’t always feel intuitive. Even more so, that Rust doesn’t have the limitations that parsers did have years ago.

enum Payment {
    Cash(f64),
    CreditCard(String, String),
    Bitcoin { address: String },
}
Slow Compilation and Large Executables

Rust compiles everything into a single static binary, which is great for deployment but leads to long compile times and bloated executables. This is acceptable for small utilities, but problematic for larger applications.

Hard to Predict Performance for Low-Level Code

In C, experienced developers can easily estimate memory and CPU costs for different constructs. In Rust, abstractions like closures, the ? operator, and trait objects obscure these costs, making fine-tuning difficult.

Lack of Clear Code Structuring

Rust’s module system feels scattered compared to the well-defined .h/.c structure in C or even OOP patterns in C++. Even popular crates (reqwest, palette) seem to lack a consistent organizational approach.

When to (Not) Choose Rust

My experience now leans more towards understanding where Rust should not be used rather than where it excels. Choosing Rust for renk was a mistake. I opted for it because I saw many new, IO-bound projects using Rust and had a similar Python program, thinking conversion would be easier than designing anew. However, Rust is not ideal for IO-bound standard software. Its low-level nature doesn’t compete well with higher-level languages like Python, Ruby, or Java for such tasks, as performance gains are negated by long IO calls.

A misleading example was uv, a tool for managing Python dependencies. Despite its popularity, the benefits of using Rust didn’t justify the complexity, especially since the primary user community is Python-based and couldn’t contribute to a Rust project. I expect that features will be hard to add, and performance gains are only notable with a “warm cache”, not your typical use case. This is not a rant against uv, I’ve tried the tool and it is awesome. Let’s just see how it ages compared to Poetry or Hatch.

Conversely, I totally support the choice of Rust for tools like eza and zoxide. eza is a modern ls alternative, and zoxide enhances cd. These commands, used frequently, must be quick and efficient. Rust provides a stable foundation, and its strong typing, rigid syntax makes contributions easier. Even the monolithic structure is here a plus.

I haven’t evaluated Rust for server software, but it seems promising. Major companies have already adopted it in this domain.

Rust in Embedded Systems: A Reality Check

The Harsh Reality of Rust on ATtiny85

I went into this expecting challenges… not a total disaster. Just setting up a Rust project for ATtiny85 meant dealing with:

  • Confusing HALs (avr-hal, attiny-hal)
  • Incompatible dependencies and undocumented feature flags
  • Required nightly Rust and dealing with compilation errors with unstable features
  • Toolchain struggles, undocumented linker flags
  • Fragmented documentation with no existing real-world examples

Even with extensive experience in embedded systems (safety-compliant C, custom bootloaders, assembly-level debugging), I found Rust’s embedded story a complete mess. Once you have all pieced together, it makes sense. But until that point, you’re in the blue. You cannot argue about safety when dealing with nightly, unsafe, and lack of documentation.

Why Rust Isn’t Ready for Embedded
  • Heavy reliance on unstable, unsafe code defeats Rust’s safety promise.
  • Absence of documentation lets you struggle alone in the blue at each issue.
  • Steep learning curve with minimal reward, given the efficiency of well-written C.

That said, Rust could become viable in the future, especially for robust top-layers applications. But today? Stick to C… and check my next article.

Conclusion: Rust is Not a Silver Bullet

Rust is an interesting language, but it’s not a universal replacement for C or Python. While it brings safety and reliability, it introduces complexity and friction that make it unsuitable for many use cases.

Where Rust Works Well
  • Small, performance-critical tools used frequently.
  • Security-sensitive applications where memory safety is paramount.
  • Large-scale backend systems needing high concurrency.
Where Rust Fails (For Now)
  • General-purpose scripting and IO-bound tools.
  • Embedded development, where stability and ecosystem maturity matter more than theoretical safety.

Rust is worth keeping an eye on. But for most projects? Stick to the right tool for the job. Let the hype settle, and we’ll see where Rust stands in a couple of years.

Related Posts

Using GNOME Keyring on XFCE on Manjaro is not complicated, but the documentation of ArchLinux doesn’t apply and you can scratch your head for a few hours before getting what you want. Well, loose no more hair, here are the simple steps.

Read More

“Hey, Pal! How are you? Remember that little project I had on the CubieTruck? I resumed hacking on it. But it’s a pity, I lost my root password… I have to start all over again. Any chance you remember what silly password we choose together?”

Read More

Last week, I was preparing a data analysis report using Jupyter, Pandas and Matplotlib (to only quote a few bricks of this wonderful framework). One of the figures had two subplots, the second being an enlargement of a region of the first. To make it obvious, and at the same time show the old MATLAB Fanclub how so 90 they were, I decided to put an arrow from the first to the second subplot.

Read More