People do sick things with lifetimes and generic associated types. Sick, sick things.
Doesn't feel that way to me.
1. Reading the error messages. I was used to error messages verbosely printing a lot of details which were mostly irrelevant and letting the programmer sort it out, and I developed a habit of skimming them. I had a better time with Rust when I realized it was giving me information that was mostly relevant, and that I should actually read them.
2. Interpreting error messages as feedback rather than failure. I was used to error messages meaning I had done something wrong, and getting them all the time was frustrating. Watching Jon Gjengset's coding videos, I was struck that he wrote code to trigger errors deliberately in order to get feedback from the compiler. I now try to work through Rust error messages the same way I would failing unit tests; I make some small changes, I knock out the errors, and then move on to the next subtask. This keeps the number of errors small and manageable, and gets me into a tight feedback loop with the compiler.
Relatedly, I encountered the idea (in a blog post I no longer remember) that the Rust compiler is more like an automated pair programming partner than other compilers. This change in mindset really helped for me; thinking of it as a friendly helper rather than a loud complainer made the work lighter. (That's why I personally don't like the 'abusive partner' metaphor, for me that mindset makes it toilsome and miserable. Also because I've had an abusive partner, and it's nothing like that, and I could do without the reminder.)
3. Setting up my IDE to show me inline type annotations and error messages. This made the feedback loop tighter, I only have to drop into a terminal for complex or unfamiliar error messages. With practice I can fix most errors using a one-line summary.
4. With practice, I've internalized Rust's semantics, and I've stopped painting myself into corners with unnecessarily cyclic data structures and such. I also read a blog article or maybe a tweet (which I've also forgotten) about giving yourself permission to use Box or clone and not sweating every copy or allocation. It pointed out that if you were writing Python, everything would be an Arc<Box<T>>, and you'd think nothing of it; in Rust the performance cost is explicit, so you agonize over it, but a lot of the time it's not a big deal to copy some data or to perform an allocation.
My first six months with Rust were very painful and I struggled to write the simplest programs. (This was in fact, the first six months of my second attempt - the first time I tried to learn it, I gave up.) Eventually I learned how to work with it, and since then, it's my absolute favorite language to work in (though presently I mostly write Typescript and SQL, because I'm writing webapps). I tried to summarize what happened along the way and what changed.
If you want sugarcoating or simply rapid prototyping I'd use a dynamic or scripting language instead.
Rust's error messages are one of a kind. There no other compiler which comes even close.
As a side note, i used latex lately, it's error messages are horrendous. What a nightmare to figure out what's wrong by inspecting the error.
Generally good, but man do I hate how any error in my async function causes every recursive call site to generate an error about how the Future is no longer Send and Sync. Literally an entire console scrollback of errors with the actual syntax error buried somewhere in the middle.
In this case, the first two errors are clear. The next 3 provide multiple, redundant context blocks. The upshot is that this simple example results in rustc printing 11 lines of useful error messages and 86 lines of useless messages. Add to that the fact that the useful error messages need not be at the top or bottom of the error list, they can be anywhere inside of it, depending on the declaration order.
We do spend a lot of time trying to silence errors that are irrelevant. We also get a lot of complaints when fixing a single error produces a wave of new errors that were hidden due to failures in earlier stages. It's a balancing act.
Also, specific errors are verbose in order to give people a fighting chance to fix their issue. An error that is too verbose is annoying. An error that is too terse will leave users in the cold as to what the problem was. It's yet another balancing act.
I filed https://github.com/rust-lang/rust/issues/117233 for you. Could I ask you why you didn't consider doing so when you first encountered this problem?
Thanks for looking into it. I have a filed a bug against Rust before, but that was a clear bug, not a poor error message. Remember, the only time a user sees something like this is when they are trying to do something else. The only reason I looked into it just now is because you explicitly asked.
My main gripe is that I still don't fully comprehend lifetimes and the compiler can't really help me every time, because it (understandably) errs on the side of caution.
I'm not sure any compiler, even one "designed for diagnostics," can gather the "necessary metadata" from inside my brain based on my original intentions. If the compiler could unambiguously interpret what I meant to write, it wouldn't have needed to fail with a compilation error in the first place. Whenever I see something like "perhaps" or "maybe" in a compilation error, the information just didn't exist anywhere the compiler could possibly get to, and it's just a suggestion based on common mistakes.