Enums and nested blocks. I understand the importance of Option and Result, but it’s fucking infuriating when I have to check and destructure the result of every function call and either bubble the result up the stack from six levels of nested iflet blocks or risk Cloudflaring my program by using .unwrap(). And while I like being able to extract a return value from an if...else expression, the structure gets really convoluted when multiple if and match blocks are nested (of course each one returning a value), and it gets completely fucked once closures are introduced.
Enums are the best part of the Rust language IMO, so I’m not sure how you can view them as ugly. Having the choice to destructure something is fantastic. You generally aren’t required to destructure every return value. Make sure you’re using the ? operator as much as possible. If destructuring is getting in your way, it sounds like the code is not very idiomatic.
I can’t really comment on your issue with nested if and match. Too much nesting is bad in any language; try extracting more functions and let bindings to make it more readable.
You can enable a clippy lint to deny .unwrap() if you’re worried about it.
Most of the times you can just let ... else (which is basically a custom ? if you neediflet ... else it’s because you actually need 2 branching code paths. In any other language you also do if ... else when you have 2 different code branches. I don’t see why this is a rust-specific issue.
But really it’s the exact same as other languages, it just forces you to handle it better. C-based languages will return 0/null/-1 and you’ll have to check all 3 of those because they might not mean the same thing. How is that better?
This isn’t about some feature of the language being good or bad. It’s about Rust being ugly or not. The things I mentioned will always look ugly in the source code.
It’s hilarious to me that people talk about “ugly” as if their opinions are objective.
I found Rust unpleasant to look at for the first two weeks of learning it, and now that I’ve been using it professionally for three years I loathe when I need to read code in other languages.
No other language can rival Rust in showing the exact information needed to understand the code — never too much and never too little — while being concise, correct, and handling all edge cases.
You can be more concise in other languages, but it will come the loss of handling every little possible bug. You can be prettier in other languages, but it will come at the price of adding a lot of useless boilerplate.
Of course there are cases where Rust can be verbose or confusing, but that’s when you’re doing very esoteric things that would be just as confusing in other languages.
Like any opinion on aesthetics, how someone feels about the prettiness of a language will have far more to do with familiarity than with any objective metrics.
Sadly there’s still no truly good way to handle errors in rust. TBH I’m not sure if we as an industry have figured out an efficient, foolproof, composable, not overly verbose way to handle errors, so it’s not entirely on Rust.
Rust allows you to choose whatever method you want.
Early return propagating the error
Early return ignoring the error (maybe by returning a default value)
Explicit handling by if-else (or match) to distinguish between error and not error cases.
Early return and turn the error into another type that is easier to handle by the caller.
Assume there is no error, and just panic if there is. (.unwrap)
There are only 2 error handling methods that you cannot do:
Exceptions
Ignore the error and continue execution
And that is because both of them are bad because they allow you to do the second one, when .unwrap is just there and better.
If your concept of “not ugly” is “I just want to see the happy path” then you either write bad code that is “not ugly” or write good code that is “ugly”. Because there is no language that allows you to handle errors while not having error handling code near where the errors are produced.
I am well aware, I wrote quite a lot of Rust, including professionally. I’m not necessarily talking about the “clear happy path”, even though that is relatively nice to have (Rust sort-of allows that with the ? sugar and the pseudo-functor and pseudo-monad methods on wrappers); I’m talking more about the fine line between the overly verbose lists of all the errors that a function could produce (thiserror-style) or just a single messy error type that makes error handling difficult (anyhow-style). There surely must be something in between there that is concise, type-safe, and composable, but I haven’t seen it in any language yet.
In my case, I don’t usually encounter cases where I can’t just ?. But when I do, just make an error enum (kinda like thiserror) that encapsulates the possible errors + possibly adds more.
On the call site, just convert to string if I don’t care about specifics (anyhow-style).
I don’t find this much painful.
Concise: not much on the declaration side, since you have to create an entire enum for each function in worst-case scenario. But on code side, it’s just .map_err(MyError)?.
Type-safe: can’t beat errors as enum values wrapped in Result.
Composable: i don’t think you can beat rust enums in composability.
I don’t use anyhow/thiserror, so I’m not sure. But I believe thiserror fixes the conciseness issue for this.
thiserror helps a bit with conciseness. But it’s still the most annoying part of writing Rust for me, even more annoying than async closure capture semantics, and one that I happily offload to deepseek whenever I have to write it. I was even thinking of making some insane proc_macro that would traverse a function and extract the list of all ?, deduce the types, and come up with the final error type (maybe with some hints). But this turned out to be too difficult for a weekend project and ain’t nobody wants to pay me to write it.
Type-safe: can’t beat errors as enum values wrapped in Result.
I’m talking more about the anyhow-style here. It’s not very type-safe.
Composable: i don’t think you can beat rust enums in composability.
Well, thiserror-style enums are composable-ish, but they can be too structured for their own good. Sometimes you want only a particular property of an error (e.g. one of the HTTP requests returned 429 or something), and don’t care about the particular branch of the call tree it came from. Rust doesn’t really have the compile-time flexibility to write such a check without a ton of boilerplate that will also break once the call tree changes.
Yeah, I was gonna say, error handling easily makes up 80+% of the code paths, and depending on whether you’re building a library or service or script etc., different strategies are most suitable for how to deal with those code paths.
In a script, you often just want it to crash. In a library, you want to make these code paths matchable, in case the user cares why something failed. And then you have the awkward in-between, which is that 99% of your application codebase will be used by your main-function like a library, but you don’t want to spend as much effort on error handling for that as a proper library does, in particular also because you know what all consumers of your application-library need to know.
So, it’s kind of multiple different problems, with overlap, and people are hoping for one easy solution to cover all these problems.
Enums and nested blocks. I understand the importance of
OptionandResult, but it’s fucking infuriating when I have to check and destructure the result of every function call and either bubble the result up the stack from six levels of nestedif letblocks or risk Cloudflaring my program by using.unwrap(). And while I like being able to extract a return value from anif...elseexpression, the structure gets really convoluted when multipleifandmatchblocks are nested (of course each one returning a value), and it gets completely fucked once closures are introduced.I like Rust, but calling it pretty is delusional.
Enums are the best part of the Rust language IMO, so I’m not sure how you can view them as ugly. Having the choice to destructure something is fantastic. You generally aren’t required to destructure every return value. Make sure you’re using the
?operator as much as possible. If destructuring is getting in your way, it sounds like the code is not very idiomatic.I can’t really comment on your issue with nested
ifandmatch. Too much nesting is bad in any language; try extracting more functions and let bindings to make it more readable.You can enable a clippy lint to deny
.unwrap()if you’re worried about it.Most of the times you can just
let ... else(which is basically a custom?if you needif let ... elseit’s because you actually need 2 branching code paths. In any other language you also doif ... elsewhen you have 2 different code branches. I don’t see why this is a rust-specific issue.You can also use let else.
let (Some(count\_str), Some(item)) = (it.next(), it.next()) else { panic!("Can't segment count item pair: '{s}'"); };But really it’s the exact same as other languages, it just forces you to handle it better. C-based languages will return 0/null/-1 and you’ll have to check all 3 of those because they might not mean the same thing. How is that better?
I like to use
unwrap_or()to define fallback values. TheOptionAPI is quite expressive.Learn how to use enum error types, how error bubbling works, and how to convert between Options and Results.
It’s Rust you are talking about, not Go.
This isn’t about some feature of the language being good or bad. It’s about Rust being ugly or not. The things I mentioned will always look ugly in the source code.
It’s hilarious to me that people talk about “ugly” as if their opinions are objective.
I found Rust unpleasant to look at for the first two weeks of learning it, and now that I’ve been using it professionally for three years I loathe when I need to read code in other languages.
No other language can rival Rust in showing the exact information needed to understand the code — never too much and never too little — while being concise, correct, and handling all edge cases.
You can be more concise in other languages, but it will come the loss of handling every little possible bug. You can be prettier in other languages, but it will come at the price of adding a lot of useless boilerplate.
Of course there are cases where Rust can be verbose or confusing, but that’s when you’re doing very esoteric things that would be just as confusing in other languages.
Like any opinion on aesthetics, how someone feels about the prettiness of a language will have far more to do with familiarity than with any objective metrics.
Sadly there’s still no truly good way to handle errors in rust. TBH I’m not sure if we as an industry have figured out an efficient, foolproof, composable, not overly verbose way to handle errors, so it’s not entirely on Rust.
Rust allows you to choose whatever method you want.
There are only 2 error handling methods that you cannot do:
And that is because both of them are bad because they allow you to do the second one, when .unwrap is just there and better.
If your concept of “not ugly” is “I just want to see the happy path” then you either write bad code that is “not ugly” or write good code that is “ugly”. Because there is no language that allows you to handle errors while not having error handling code near where the errors are produced.
I am well aware, I wrote quite a lot of Rust, including professionally. I’m not necessarily talking about the “clear happy path”, even though that is relatively nice to have (Rust sort-of allows that with the
?sugar and the pseudo-functor and pseudo-monad methods on wrappers); I’m talking more about the fine line between the overly verbose lists of all the errors that a function could produce (thiserror-style) or just a single messy error type that makes error handling difficult (anyhow-style). There surely must be something in between there that is concise, type-safe, and composable, but I haven’t seen it in any language yet.In my case, I don’t usually encounter cases where I can’t just
?. But when I do, just make an error enum (kinda like thiserror) that encapsulates the possible errors + possibly adds more.On the call site, just convert to string if I don’t care about specifics (anyhow-style).
I don’t find this much painful.
Concise: not much on the declaration side, since you have to create an entire enum for each function in worst-case scenario. But on code side, it’s just
.map_err(MyError)?.Type-safe: can’t beat errors as enum values wrapped in Result.
Composable: i don’t think you can beat rust enums in composability.
I don’t use anyhow/thiserror, so I’m not sure. But I believe thiserror fixes the conciseness issue for this.
thiserror helps a bit with conciseness. But it’s still the most annoying part of writing Rust for me, even more annoying than async closure capture semantics, and one that I happily offload to deepseek whenever I have to write it. I was even thinking of making some insane proc_macro that would traverse a function and extract the list of all
?, deduce the types, and come up with the final error type (maybe with some hints). But this turned out to be too difficult for a weekend project and ain’t nobody wants to pay me to write it.I’m talking more about the
anyhow-style here. It’s not very type-safe.Well, thiserror-style enums are composable-ish, but they can be too structured for their own good. Sometimes you want only a particular property of an error (e.g. one of the HTTP requests returned 429 or something), and don’t care about the particular branch of the call tree it came from. Rust doesn’t really have the compile-time flexibility to write such a check without a ton of boilerplate that will also break once the call tree changes.
Yeah, I was gonna say, error handling easily makes up 80+% of the code paths, and depending on whether you’re building a library or service or script etc., different strategies are most suitable for how to deal with those code paths.
In a script, you often just want it to crash. In a library, you want to make these code paths matchable, in case the user cares why something failed. And then you have the awkward in-between, which is that 99% of your application codebase will be used by your main-function like a library, but you don’t want to spend as much effort on error handling for that as a proper library does, in particular also because you know what all consumers of your application-library need to know.
So, it’s kind of multiple different problems, with overlap, and people are hoping for one easy solution to cover all these problems.
Sum types with associated values are worth it
I’ll start using this one