Why Ive Fallen Out of Love With Rust

Why I’m Falling Out of Love with Rust

Rust has a reputation for being a modern, safe, and efficient systems programming language, and I initially bought into that hype. However, as I spend more time with Rust, I find myself increasingly frustrated with its complexity and design decisions. What once seemed like a breath of fresh air now feels like an exhausting exercise in fighting the language. Here are some of the biggest pain points that have made me reconsider my enthusiasm for Rust.

Syntax That Borders on Unreadable

Rust’s syntax is often heralded as expressive, but I find it to be unnecessarily complex. The sheer amount of symbols, brackets, and lifetimes annotations makes even moderately complex Rust code difficult to read at a glance. Compare this to Go, which is designed for simplicity, or TypeScript, which balances expressiveness with ease of readability.

Example: Rust vs. Go vs. TypeScript

Rust:

fn get_value(map: &HashMap<String, i32>, key: &str) -> Option<&i32> {
    map.get(key)
}

Go:

func getValue(map map[string]int, key string) (int, bool) {
    value, ok := map[key]
    return value, ok
}

TypeScript:

function getValue(map: Record<string, number>, key: string): number | undefined {
    return map[key];
}

Go and TypeScript prioritize clarity, while Rust’s syntax, with its explicit borrowing and references, feels significantly more convoluted.

Too Many Ways to Do the Same Thing

Rust prides itself on giving developers control, but sometimes it feels like too much control. There are often multiple ways to achieve the same result, each with different performance and safety trade-offs. Do I use Rc<T> or Arc<T>? Should I reach for Box<T> or just take a reference? Do I need Cow<T>? The choices are overwhelming, and instead of making development easier, they often introduce unnecessary cognitive overhead.

Example: Ownership Management in Rust vs. Zig vs. Go

Rust:

let x = Rc::new(5);
let y = x.clone();
println!("{}", y);

Zig:

var x = 5;
const y = x; // Simple assignment
print("{}", .{y});

Go:

x := 5
y := x // Simple copy
fmt.Println(y)

Zig and Go take a more pragmatic approach, avoiding unnecessary complexity while still ensuring safety.

Lifetimes: A Constant Struggle

Lifetimes are one of Rust’s most defining features, designed to eliminate entire classes of memory safety issues. However, actually working with lifetimes is often a frustrating, mind-bending experience. Compare this to Zig, which has manual memory management but avoids the complexity of lifetimes.

Example: Rust’s Borrow Checker vs. Zig’s Simplicity

Rust:

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

Zig:

fn longest(s1: []const u8, s2: []const u8) []const u8 {
    return if (s1.len > s2.len) s1 else s2;
}

Rust’s explicit lifetime annotations add complexity that Zig sidesteps entirely, making similar tasks much easier in Zig.

Async and Lifetimes: A Nightmare Combination

Rust’s async model, while powerful, adds an additional layer of complexity when combined with lifetimes. Borrowing across async functions can quickly lead to tangled lifetimes and compiler errors that feel nearly impossible to resolve. Compare this to TypeScript, which makes async programming straightforward.

Example: Rust Async vs. TypeScript Async

Rust:

async fn fetch_data() -> Result<String, Box<dyn Error>> {
    let response = reqwest::get("https://example.com").await?;
    let text = response.text().await?;
    Ok(text)
}

TypeScript:

async function fetchData(): Promise<string> {
    const response = await fetch("https://example.com");
    return response.text();
}

In TypeScript, async/await works as expected, while Rust introduces additional complexity due to its ownership model.

Manual Memory Management: Why Am I Doing This in 2025?

One of Rust’s biggest selling points is its lack of a garbage collector, allowing for precise memory management. But in day-to-day development, I often find myself wondering: why am I manually managing memory when other modern languages have figured this out?

Compare Rust’s manual memory management with Go’s garbage collection and Zig’s manual but ergonomic memory handling.

Rust:

let x = Box::new(5);
println!("{}", x);

Go:

x := 5
fmt.Println(x)

Zig:

const x = allocator.create(i32);
print("{}", .{x});
allocator.destroy(x);

Rust requires explicit heap allocation and deallocation, whereas Go handles it automatically, and Zig makes it explicit but ergonomic.

Conclusion

Rust is an impressive language with admirable goals, but it’s not without its flaws. Compared to Go, TypeScript, and Zig, Rust often feels like an unnecessary struggle. While some developers thrive in its strict, safety-first environment, I find myself more and more frustrated with its complexity and steep learning curve. As I continue to work with Rust, I question whether the benefits outweigh the headaches, and whether it’s truly the right tool for my needs.