Comment by throwaway894345

18 hours ago

I’ve repeatedly tried using Rust and the error handling has tripped me up every time and has been ~90% of the reason for moving a project back to another language. I’m sure I’m just holding it wrong, but what I run into usually goes something like this (mind you, I have read the Rust book):

* Someone tells me to use enums for errors, in a comment like yours

* I try writing the enums by hand, implementing the error trait

* I realize that in order to use the ? operator I need to implement From on my errors (I’ve read so many comments about how awfully verbose Go errors are, so I assume I’m supposed to use ? in Rust). There are also some other traits IIRC but I’ve forgotten them.

* I realize that this is pretty tedious, manual work, so someone points me to thiserr or similar

* Now I’m debugging macro expansion errors and spending approximately the same amount of time

* I ask around and someone tells me not to bother with thiserr and to just write the boilerplate myself or else to use anyhow or boxed errors everywhere

* I try using boxed errors everywhere, which works, but now I have all of these allocations which feels like I’m doing something that will bite me later. Oh well, but now I need to annotate my errors so I can figure out what is actually happening. I guess I should use anyhow for this?

* Anyhow mostly works but this is approximately as verbose as the Go error handling that I’m told is Very Bad, and when I ask for code review most Rust people are telling me not to use anyhow because errors should be enums, at least in the API surface

I’m sure I’m doing it wrong, but as with many things in Rust, the Right Way is so rarely clear and every other Rust person gives different advice about how to solve my problem and the only thing they seem to agree on is that Rust has an easy solution and that I’m following the wrong advice. (Similarly when I had lifetime problems and half the community told me to just use clone and Rc everywhere until I had performance problems, so instead I just had different static analysis problems).

I don’t love Go’s error handling. It feels like there has to be something better than its runtime-typing. But it largely gets out of the way—creating an error is just implementing the Error method, and if you need a concrete type you use Is/As/AsType. Wrapping is fmt.Errorf. All of this is built into the stdlib and used pretty ubiquitously across the ecosystem—I don’t run into “this dependency uses a different error framework”. Error handling is marginally more verbose than with Rust if you are actually attaching context in both, and neither solves the problem of which call frame attaches the context about specific function parameters (e.g., which level of error context specifies that the function was called with path “/foo/bar.baz”). It’s terrible, but it works—feels like the least bad thing until the Rust community can arrive at some consensus and document it in The Book. Or maybe I just need to try again in the LLM era?

there are many (right) ways for writing monad transformers, and it's usually situation dependent which one makes sense. (practical aspects such as which errors do you want to merge, ignore, provide some default/fallback result; and of course overall coding style consistency helps guide this, but it's not trivial.)

(there's a lot of this in Scala too, because of the various monads/containers, eg. the built-in Future, and then Scalaz.IO, FS2, Cats, ZIO, etc...)

regarding lifetime and performance problems, the best practice seems to design the rough scaffolding of the program first, with the structs, so the who owns whom can be figured out. but this is far from trivial. Rust is very good at forcing developers to stare at these problems, but solving them requires practice and patience.

for me the tech toolbox that makes sense is TS by default (because of the super convenient type system and tooling), and Rust when the circumstances really justify it (latency, throughput, scalability, cost effectiveness, or a need for a single native executable [though nowadays this is also pretty simple with Deno], or more safety/control [no GC])

How come you get macro expansion errors? Or is it because you write incorrect syntax in the enum error definitions?

The example on the docs page is quite clear:

https://docs.rs/thiserror/latest/thiserror/#example

Including all kinds of errors: Strings, tagged unions and automatically converting from std::io::Error with added context.

That one page document is the entire documentation for the thiserror crate.

I'm so perplexed by this, because Rust errors are what make the language so amazing.

> Now I’m debugging macro expansion errors and spending approximately the same amount of time

This never happens once you've learned the language a bit more. Anyhow and thiserror are a cinch.

> I realize that this is pretty tedious, manual work, so someone points me to thiserr or similar

Claude writes Rust so effectively. It can do all of this for you now. It's effortless. In fact, I don't see any reason to use any other language unless I'm targeting web or some specific platform, or dealing with legacy code. Rust is now the best tool for most problems.

> Similarly when I had lifetime problems and half the community told me to just use clone and Rc everywhere until I had performance problems, so instead I just had different static analysis problems

Do this for a month, then it'll click and be second nature. Also Claude will make quick work of it now.

> feels like the least bad thing until the Rust community can arrive at some consensus and document it in The Book

It's difficult because it's so different. But once you get used to it, you'll realize it's the best approach we have right now.

> Or maybe I just need to try again in the LLM era?

Seriously this. You'll be writing Rust code as quickly as you would Python code. It'll be high quality. And the type system will mean that Claude emits better code on average. You'll pick it up quickly.

  • I think Claude may be what makes me use Rust successfully. Firstly it’s ability to deal with the tedium and secondly not needing to solicit help from people who tell me my problem is trivial while giving contradictory solutions :)

    > And the type system will mean that Claude emits better code on average.

    I’m curious if this is true. I believe that it emits better code than with a dynamically typed language, but as with people I don’t know that the sweet spot is at the extreme. Or maybe it is at the extreme when the context is small but as the context grows perhaps code quality suffers as it has more constraints to balance?

    • > Firstly it’s ability to deal with the tedium and secondly not needing to solicit help from people who tell me my problem is trivial while giving contradictory solutions :)

      I'm so sorry for this btw.

      The problems are trivial once you've used Rust for n hours, for some value n. It's just that these folks forgot the learning and headache they went through.

      You're going to build that same recognition and familiarity using Claude over time. It'll seep in pretty quick, I'd imagine.

      > I’m curious if this is true.

      Being forced to emit an Option<T> or Result<T,E> and then having to actually use syntax to get at the goods forces the code to deal with errors the appropriate way, clearly, idiomatically, and typically in a good flow that is amenable to readability and easy refactoring. Other languages without Option, Result, and sum types baked into the language so fundamentally do not have this advantage.

      I feel it every time I have to work in a TypeScript codebase, for instance. It's a strongly typed language, and can emulate sum types via discriminated unions. But that doesn't convey the same advantages because it doesn't enforce anything. It's far too lose to have the same advantages Rust has.

      I think you'll feel the same way as you use the language more and more.