upvote
> Rust has three main error systems (io::Error, thiserror, and anyhow), which is a pain when you have to pass them upward through a chain of calls

anyhow explicitly isn't designed for what you are trying to do here. It's designed to be the last link in the chain (and complementary to thiserror, not in competition). If you are using anyhow any deeper than your top-level binary crate, you are likely to be in for an unpleasant time.

reply
also thiserror isn't incompatible with io::Error? all thiserror does is do code generation for "typical" enum errors. The errors it generates are normal rust code though, and the fact that they're generated by thiserror shouldn't really matter?
reply
He was talking about the chain of function calls not crates. You still have that in your top level crate.
reply
I love Go and used to write it heavily for anything non LLM based.

Now that we have agentic coding I just write everything in Rust and couldn’t be happier. The struggle with rust was writing it, go was made so it was easy to write for mid level engineers. Now that we have agentic coding I’m not sure Go’s value prop holds up anymore

My rust services have been nothing short of amazing from a performance and reliability perspective

reply
In my experience LLMs (I speak mainly of Claude Code & Cursor) write very poor quality Rust.

They treat it like it's JavaScript, falling back to using String/&str needlessly instead of making new types. They do ugly `static Mutex<Refcell<` a-la global JS variables for info sharing instead of working out the lifetimes to do it properly. It loves making functions infallible and then panic-ing within them and certainly I wouldn't use them for unsafe at all - they hallucinate safety comments which are in fact, totally unsound.

Of course these are all surmountable with an experienced developer to regularly step in and unfuck the code, but forcing them into 'harder' territory where every problem is not solved by a .clone() and an Arc<Mutex<>> means they will spend minutes 'thinking' about basic lifetime issues until I step in and add the missing `move` in a closure.

reply
LLMs generally write poor quality anything. It'll [usually] work, but it'll need massive refactoring to get in a maintainable and efficient state.
reply
That is interesting. I make LLMs write C with the general hope that a simpler language they can manage well. That is not entirely true, though. They reason about C fluently indeed. The problem is, Claude pumps lots of bad C into the codebase if left unattended for 5 min. So, I need some clean-up passes afterwards to get to some acceptable quality level (both by LLMs and my own eyes). At which point, Claude sees the problem clearly, for some mysterious reason. Also, I use a C dialect heavly influenced by Go (slices, generics, no smart tricks, virtually no malloc).
reply
You gotta know how to write Rust (and general software arch) first. LLMs + Rust have been great for me.

"Write an SQL Repository with this interface"

Sweet - no need for SQLc or an ORM

reply
same experience. any good claude skills to ease the pain?
reply
deleted
reply
Maybe true 6 months ago, but now it's better than any Rust Dev, as long as you guide it and not just let it rip on a full service/app unsupervised.
reply
It's better than any Rust dev... as long as you guide it

What?

reply
For me the bottleneck now is reading/reviewing code, not writing code. As you said, AI makes it way easier to write, but do you not review the code? And isn't a verbose, cryptic language with lots of nitty gritty memory management not harder to read/review?

I'm not sold on Rust being a great language to use with AI unless the reason to use it is a lot more than just Rust being fashionable.

reply
I find Go harder to review than Rust.

The verbose error handling diluting the interesting parts is one thing, but the main issue is the weak type system. Having to read the callee's code to check if it deviates from `res xor err`, or if it mutates its arguments. Figuring out which interface that `func (o *Obj) ()` is implementing, if any. Dealing with documentation that is a wall of 100 disappointing oneliners all repeating the function name.

Rust is information-dense and takes longer to master, but it's not inherently cryptic, there's a finite amount of things to know. Memory management sometimes take a bit of thought to write, but it's straightforward to review, you can trust it's correct if it compiles, you just keep an eye out for optimizations.

reply
I don't see the difference in exploring an dense custom type system versus a flatter one. Both force you to look things up when you don't know about them.

In my opinion these problems originate in architectural style. Much of the open source written today is designed to impress the audience instead of focusing on the problem.

reply
In my experience, Rust is only mildly unpleasant to review, if only because the GitHub PR review interface is not an IDE. It can be hard to tell why .as_ref()s and whatnot had to be used without being able to hover over a variable to see its type. This is probably because of the language's preference for type inference, though personally I would rather that than having to skim over explicit types.

Compared to Rust, Go as a language requires a lot more effort to review. You have to be on the lookout for basic gotchas like not checking if a pointer is nil, placing `defer` in the wrong place, using a result when err isn't nil, and so on. Plus, diffs are messier because unused variables are a compilation error, and _, err := can change into _, err = solely due to new lines above.

reply
And := not being = can really matter for variable shadowing.

Absolutely insane syntax choice in a language where everything returns 2 values. At least do var:, err: =

reply
It's the same logic for human and for AI code: In Rust the compiler catches many bugs so you don't have to.

If the LLM gives you safe code you know there are entire classes of things you don't have to review for.

That said, I agree with you. My experience is that LLMs are great if you are highly competent in the domain in which you let them work. And it's probably easier to be competent in Go than in Rust.

reply
Safe? No compiler is going to catch badly designed code, or intentionally backdoored code. Memory leaks as well. Compilers are the ground floor of validation and the least of your problems with AI generated code.
reply
Yes? Does that contradict anything I said?
reply
A mythical compiler might catch unsafe code
reply
I found it's the opposite. Thanks to LLM's whole classes of problems otherwise solved by using Rust are gone. It's now more important that the generated code is easy to read.
reply
The build time and space for rust is awful for fast iteration e.g change a thing, verify.
reply
Reading the code? Who has the time.

Aah, I am sure the chickens of vibe coded origin, will never come to roost.

reply
Time isn't the constraint here, but ability. Someone complaining about how hard Rust is to write is probably not capable of reviewing Rust code very well.

The usual reaction or opinion from e.g. good C++ programmers switching to Rust is that the added guardrails and expressivity are great and make things easier.

reply
I find it easier to review Rust (but Go too) than to write it.
reply
IMO neither Go nor Rust are great for reading/reviewing code.

Go is too verbose and the type system isn't expressive enough. Rust code is littered with little memory management details and it requires tons of third party libraries.

I think coding agents will eventually be able to get the low level details right on their own. Reviewers should be able to focus on architecture, design and logic mistakes.

I also think we need a high level formal specification language to tell agents what we expect them to do.

reply
> I also think we need a high level formal specification language to tell agents what we expect them to do.

Let’s make that specification Turing complete while at it.

Jokes aside, IMO it will be a good natural progression. Specify the problem statement in LLM specification, generate the code in Go/Rust whatever is the language of your choice and review the generated code to make sure it adheres to the architecture/design principles that you have set.

reply
It absolutely should be Turing complete. I want to formally specify some constraints/invariants that any generated code has to meet, like very high level test cases.

It doesn't have to be a new language. I'm sure some existing language can be used to create a DSL that serves this purpose.

It can obviously never be complete. Some parts of the spec will always have to be natural language if we want to make the best use of LLMs.

reply
Maybe we can have Large Logic Models instead, and they could have formalized keywords with rigid meanings? Like IF, WHILE and FOREACH maybe. Or even ASYNC if you want to be modern about it.
reply
I do think that AI models should get better at logic. But if code generators are supposed to be tools, we have to tell them what to do. I'm not sure what combination of languages is best for that purpose.
reply
It would be so much easier if we could precisely specify what we wanted, without all the double meanings, slang and general ambiguity that comes from using a natural language.

If only there was an entire class of well-studied languages which don't have any such ambiguity. They'd be perfect for programming LLMs! We could call them "programming languages" perhaps.

reply
But what we want is a lot of ambiguity on the implementation side and some targeted ambiguity on the specification side where appropriate.
reply
The benefit is if you lean heavily on types then successful compilation is a massive indicator in the feedback loop. Using stop hooks to ensure successful compilation after every iteration is a game changer. Go also has compilation of course but because the type system is so much more robust in Rust the compilation guarantees so much more about the behaviour of your program. You end up just code reviewing the shape and flow of data.
reply
Code compiling is really the lowest bar of code validation, and doesn't say much of anything of the code running correctly. AI will pump out the most convoluted, over engineered, and at the same time sloppy code if you let it - and it will all compile fine.
reply
Yes, but all else being equal it is a higher bar in Rust than in Go. There are fewer things left for the human to check after a clean build+lint in Rust than in Go. The issue of over-engineered AI output is orthogonal to that.
reply
Overengineering and convoluted code stand out when reading. The hard part are the subtle errors. And the Rust compiler helps you out a lot more here
reply
It's the lowest bar and that's precisely why you want it to be as high as possible.

For me, one of the bigger complaints is that Rust isn't pedantic enough. Panic free Rust isn't taken seriously enough as an idea.

I wish it would catch even more things, since it works so well.

reply
Go was never about being easy to write (thought it is), but it was always about being easy to read and it is, by far, the easiest language to read that I've ever used (and throughout the decades, I went through Basic, Pascal, C, Java, JavaScript, C#, TypeScript, Ruby and Python). That becomes even more important if you are not writing the code yourself...
reply
it's too verbose, yet not explicit.

you need to know the conventions to spot what's not there (did you miss the error handling? or the magic comment for the whatever codegen serializer? c'est la vie!)

edit: just a few comments below an even better description of what I'm trying to convey: https://news.ycombinator.com/item?id=48264853

reply
Disagree, if you miss the error handling the code won’t compile (unless you return _, which should be easy to spot)

The magic comment stuff is very much “do it once” and it’s done (for example if using go generate).

reply
[dead]
reply
> The struggle with rust was writing it, go was made so it was easy to write for mid level engineers.

In practice, anything that makes it easier for humans to program also makes it easier for LLMs to program.

You also wont typically learn that the LLM is close to the limits of understanding your code base until after it has blown past it's own capabilities, leaving you with a mountain of code that you are not skilled enough to fix.

Java, C# are good choices as they tend to enforce a certain structure. Go, good because it's very readable even if you dont know the language.

C++, Rust are poor choices unless you are already a senior in that language.

reply
Go is really easy to read and write, even if Go's philosophy means that some of that feels clunky because it's less featureful than other languages. It makes up for it with a comprehensive stlib that makes it trivial to build services with few to no third party dependencies.

I don't think the value prop has changed at all there. One day the AI gravy train will stop and people who used AI to punch above their weight will no longer be able to debug the stuff they built unless they put in the hard work of learning the language.

Nothing to worry about with Go in that respect because of how much it's been designed to be simple. Even the annoying err/nil checks you need to do all the time are in service of that simplicity. It gets old fast but it leaves nothing to the imagination.

reply
The value prop of Go in not on writing, but in reading and comprehension by people different from the autor(s). For systems expected to last some years, this translates in reduced total cost of maintenance over the life of the system (in my experience typically 80% or more of the total cost) and facilitating traspassing maintenance to diferent people than the authors. In use cases where Go has "good enough" performance, for backend systems with business logic and small amount of "bare metal" programming, I recommend Go to teams instead of Rust. When extreme performance and reduced memory footprint is more important than the other properties, Rust is better than Go.
reply
>Now that we have agentic coding I just write everything in Rust and couldn’t be happier. The struggle with rust was writing it, go was made so it was easy to write for mid level engineers. Now that we have agentic coding I’m not sure Go’s value prop holds up anymore

Agents seem to have a better time with Go. Humans need to review the agents outputs and in general they have an easier time to do it with Go.

reply
> Rust lacks a uniform error type

Rust has practically one error, it's the Error trait. The things you've listed are some common ways to use it, but you're entirely fine with just Box<dyn Error> (which is basically what anyhow::Error is) and similar.

reply
Having many semantic options for error usage is functionally the same as having many error types, except worse.
reply
Please go write C++ and then come back to us
reply
I spent 5 years writing C++.
reply
They all convert seamlessly, and the enums make the branches explicit. Don't even need to check the documentation to find which errors supposedly exists like in Go with its errors.Is, errors.As, wrapping and what not.

An easy rule before you make a knowledge based choice is Thiserror for libraries, helping you create the standard library error types and Anyhow for applications, easy strings you bubble up.

Or just go with anyhow until you find a need for something else.

https://crates.io/crates/anyhow

https://crates.io/crates/thiserror

reply
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?

reply
> “this dependency uses a different error framework”.

Common in HTTP land. The HTTP system returns a different error type than the network I/O system, but they can be sorted out.[1]

[1] https://github.com/John-Nagle/maptools/blob/main/rust/src/co...

reply
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])

reply
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.

reply
It’s been a while, I don’t remember the details, but it wasn’t syntax errors.
reply
a few things

1. thiserror just does codegen of the "standard" enum things people do. if you find debugging thiserror difficult, just write out the enums manually. sure it's uglier, but (roughly) equivalent. so its preferable as synctatic sugar for enums, but doesn't have any technical benefits (in the same way that syntatic sugar never really does).

2. for boxed errors, you only get allocations on your error path. Hopefully this is a cold path so it shouldn't matter.

There is a general theme behind rust error handling though which it can be good to internalize. In particular, the more details of your errors you encode in the type system, the more powerful things are. Any error type could just be

pub struct MyError(String)

the issue is that this gives very little information to a caller on what to do with your error. If you have no callers (e.g. are making a binary) it's fine, and (roughly) what `anyhow` does.

That all being said, when designing errors a natural question to ask is "can my caller do anything meaningful with this error"? For example, in Rust stdlib, `Vec::push` can allocate. This allocation can fail, which panics. "Proper" error handling would use the fallible allocation API, and propagating an OOM error or whatever through results. For most applications, this is not an error that is worth investing that much time into guarding against, so using the (potentially panicing) `Vector::push` makes things easier.

You can take this same perspective in other settings as well, in particular separating out errors into

1. structured data, that a caller should be able to extract/process to handle the error, and 2. unstructured data, that is more used for logging, and you expect the caller to pass up the call stack without inspecting themself.

Handling both types of these errors with `thiserror` can be tedious for little benefit. I've found it useful to instead solely use `thiserror` for category 1, and category 2 does other things. This could be using `.expect(...)`. There are some crates that make this nicer (e.g. `error_stack`). But the point is that it can significantly clean things up if you only encode in your error enums failures that you expect someone to handle, rather than just e.g. log.

This does somewhat validate your point that the "right way" I've been experimenting with (and mentioned above) is not just "use this-error".

Also: a big issue with `thiserror` is the tedium of handling the large error enums (or giving up on using it "properly" and shoving together multiple error variants in some unstructured error type). that is somewhat better in the LLM era, as you can have the LLM handle the tedium.

reply
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.

reply
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?

reply
> 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.

reply
[flagged]
reply
[flagged]
reply
I know you’re trying to snark, but I’m clearly thinking for myself—that ought to be evident from the first sentence of my post. :)
reply
Apologies for the phrasing, I should have written "form your own judgment"
reply
Surely you need an alternative to Box<dyn Error> for reporting memory allocation failures?!
reply
Anything other than panic/abort on allocation failure is outside the scope of the vast majority of programs, including anything using the standard library in Rust. I wouldn't worry about Box<dyn Error>.
reply
A &(dyn Error + 'static) should be fine for that; you don't need any allocated/variable sized data in a memory allocation failure.
reply
stacktraces? might also be useful to know whether or not the latest allocand was a jumbo sized allocand that caused the failure?
reply
Do you really want that data passed back down to the caller of the allocation? From the description of the failure state you'd want to log that data instead: what's the caller of the allocation going to do if you tell it it failed with a crazy size? It already knows the size, it's the one who asked for it.
reply
So, suppose it's a rust library -- you're locking me into whatever logging system the library author chooses? Maybe I'd like to consume the relevant data at the entry point and send it to a logging system of my choice.
reply
A Rust library likely wouldn't be returning an opaque Box<dyn Error> to begin with. Errors are part of a library's API—it's what allows consumers to handle them—so you'd define an enum of possible errors your library could produce and return that, which would be stored on the stack.
reply
What about the data in the error payload?
reply
I think this is a clash of terminology: a Rust enum isn't an integer with pretensions of an identity.

You'd describe it as a tagged union in some languages. So when you say you'd return an error with extra information, what that information is is associated with the specific variant of the enum.

Using yuriks AllocError as an example, if the error is SizeTooLarge, it has the size field. Other errors may have no additional data, others may have different data.

When you return an error from your allocating function, it's a known size, the size of the largest enum variant + the discriminant (tag).

reply
That's part of the error enum.

  enum AllocError {
    SizeTooLarge { size: usize },
    // etc.
  }
This enum has a known size and doesn't require any dynamic allocations.
reply
You can do better than the errors in other languages. You can provide all the relevant information in the emum variant.

  enum MyApiBindingCrateError {
    // You didn't provide an 
    // API key. Maybe we should 
    // design our interface to
    // make this impossible 
    ApiKeyMissing,

    // Client was unauthorized 
    // to make this request 
    AuthorizationError,

    // The entity you requested 
    // did not exist (404'd)
    NotFoundError,

    // You're sending too many 
    // requests to the server 
    TooManyRequests,

    // That specific error with
    // the API
    // Maybe users can't delete 
    // folders until they're empty 
    // Whatever 
    SpecificApiIssue1,

    // Some other specific error
    // with the API
    SpecificApiError2,
  
    // Server didn't respond the
    // way we expected. 
    // Here's what it told us
    UnexpectedHttpResponse { 
      // HTTP status code
      status_code: StatusCode, 

      // If it had a 
      // string-encoded body
      body: Option<String>,
    },

    // Unhandled Issue with IO 
    IoError(io::Error),
  
    // Unhandled Issue with 
    // request library
    ReqwestError(reqwest::Error),

  }
The beauty with Rust is that you can create really detailed concrete errors at the crate level. Your callers will know exactly what the actual error states are.

Your application can be a little less structured if you want. Though with LLMs, I'm using anyhow and thiserror a lot less.

reply
In the current context with regards to failed allocations, you're also supposed to add a variant that wraps AllocError.
reply
it depends, if the functionality represented by the library is known to require a lot of memory (or simply allocation failures are an expected part of its operation), then it should be pretty much part of the API, probably with some tracing/diagnostics interface to get the required visibility into how much memory goes and where.

but for most libraries I on allocation failure I don't expect any fancy logging system. maybe even panic is fine.

reply
usually “stdout” is good enough, wrapper/runner routes output to logserver for collation and search. who cares about formats as long as it’s reasonably structured and searchable?
reply
And how do you store a stacktrace without allocating?
reply
Any time I mention "but I would like stacktraces with my errors" I get told I'm doing it wrong.
reply
That's because the types of errors where you want a stack trace are a relatively small subset of all possible errors.

Stack traces are only useful for errors that indicate a bug in the program, i.e. something a programmers has to respond to. It's not useful for the vast class of bugs that are a result of wrong input, wrong external state, or infrastructure issues.

Rust projects tend to favor panicking over error handling for programmer bugs (which does indeed give you a stack trace depending on environment variables), or even better encoding the invariants in the type system, but there are cases where an error coming from a library are truly, actually unexpected, so both `anyhow` and `thiserror` do provide support for attaching a stack trace in those situations.

reply
See? You get people explaining to you that you actually don't want a stack trace because xyz.
reply
If you let the allocation error panic you will get your stack trace.

You can't have a stack trace on an error in the error path that failed to allocate. If you have a "jumbo sized" error and the error fails to allocate, it won't get reported. The only reporting you will get is that the error failed to allocate and this new allocation error overrides the error that failed to allocate.

reply
You're already writing Rust in a very different style if you're writing the type of code that gracefully handles allocation failure. It's to Rust's immense credit that this type of coding is actually fairly well-supported (unlike in Go), but you're already a bit off the beaten path for stuff like error handling.
reply
Not sure what your problem is?

If you need to handle an allocation error in the error path, then the error reporting path must abort, which means that the allocation error must be bubbled up.

There is no real solution to an allocation error inside the error path. Even if you preallocate an arena for errors, the error might be large enough that it won't fit inside the arena.

Hence the best thing you can do from that point onwards is to have an error enum with an AllocError variant that doesn't allocate. Said error won't contain any information beyond line numbers of the allocation error since you just don't have the space for it.

In the end you will basically end up with panic free code, but the error still bubbles up like regular unwinding.

So yeah you can do it, and I will do it in the future, but I personally think that the people who think this is some huge deal breaker don't understand the problem in the first place.

reply
For me the main advantage of Go over Rust is compilation speed. Then compared with Go Rust still rely on many C and C++ libraries making it problematic to cross-compile or generate reproducible builds or static binaries.

The minus side of Go is too simplistic GC. When latency spikes hit, there are little options to address them besides painful rewrite.

reply
Go has the Green Tea GC since 1.25. It’s no longer simplistic and quite well engineered. The AVX-512 support was especially interesting to me.

https://go.dev/blog/greenteagc

reply
I've run into GC pauses, I think in many (most?) cases there is some class of bulky data that you can either move into slices of pointer-free structs (so the GC doesn't scan them) or off-heap entirely. The workload where GC is slow is also likely prone to fragmentation so whatever the language you'll have to deal with it.
reply
Java with its copying GC deals fine with fragmentation albeit at the cost of more upfront memory. And even in Rust one can change the allocator to try to deal with fragmentation. But with Go there is simply no good options besides the rewrite.
reply
Possibly in your specific application, usually there are a handful of options far less painful than a rewrite.

For the original issue of GC pauses, a narrow change is to move problem data to non-pointer-carrying types, or the bigger hammer of manually managed slices of those types. The second helps with fragmentation too. Some workloads can be split into multiple processes as a direct way to have smaller heaps. If none of those options are enough then off-heap storage lets you do whatever you want.

I do have some complaints about Go, but one of the big ones has been fixed since I last wrote much Go code and it seems like a fine choice for a lot of applications.

reply
> For me the main advantage of Go over Rust is compilation speed.

Interestingly, Rust has quite good failed compilation speed. That's almost good enough. The usual Rust experience is that it's hard to get things to compile, and then they work the first time.

reply
I've never been bothered by long compilation times, it gives me time to think about what the code should actually do.

To other people's usage patterns though, I imagine the group of people who don't do much with the type system rely more on running a built binary to see if it worked, which means they'll pay the full compile/link time cost more often.

reply
What kind of apps are you writing where GC spikes matter?
reply
trading, networking, gaming, ai, realtime, almost anything with hard response requirements.
reply
Rust compilation speed is a matter of tooling, they could have something like OCaml or Haskell interpreters, which so far hasn't been a priority.

Or having Cranelift as default backend.

reply
if you are hitting pauses due to GC issues, you should into putting appropriate data structures into a memory arena, here's a reasonable read:

https://uptrace.dev/blog/golang-memory-arena

These are all tools. Java used to have this all the time, and we (ex-java programmer) had ways around this until the JVM improved.

reply
Isn’t it somewhat easy to remove allocations in Go? I haven’t had to “rewrite” as such, but rather lifting some allocation out of loop. Am I misunderstanding the scenario?
reply
Pauses are a problem with heap size and structure, not allocation rate, because the pause is caused by GC code that is O(heap size). Making garbage slower reduces the frequency but not severity. This is an issue with most GCs to some degree, there are phases of collection where the GC stops execution and the duration is relative to how much work it has to do which is based on how many objects and how much memory needs to be checked. "Concurrent" garbage collection is the approach of trying to reduce the pauses by doing more of the work while program execution continues. It's complicated and hard to get right, so Go's original GC was IIRC fully stop-the-world.

There are some fine points to the O(heap size), for example it's clearly unnecessary for the GC to scan objects that do not themselves contain pointers, and work is somewhat proportional to the total number of objects. Combining numerous small objects into manually managed slices, coming up with ways to make the most numerous items pointer-free, etc.

I learned a bit about this when an analytics workload I had ended up with unacceptable pauses (I think over 1 second), Go's GC is more sophisticated now but I think in any GC runtime you have the same considerations to some degree. Some of the best writing at the time was by Gil Tene, one of the principal authors of the C4 concurrent collector at Azul Systems, starting point here:

https://groups.google.com/g/golang-dev/c/GvA0DaCI2BU/m/SmEel...

reply
With backend serving many clients with widely varying performance profile of individual requests when latency spikes happen there is no particular hot loop. Just many go routines each doing reasonable thing but with a particular request pattern hitting pathological case of GC.
reply
Extreme variance in usage patterns will always be challenging. But pre allocating some reasonably sized buffers can go a long way.
reply
Removing enough allocations to avoid fragmentation can be maddenly difficult/tedious.
reply
> Rust still rely on many C and C++ libraries

Yes but Rust has a lot more availability of libraries to do stuff as a result. Want to do anything ML or scientific? You at least have a route in Rust where you don’t with Go.

reply
With Go basic stuff like url parsing or HTTPS support is written in Go and comes with the standard library. With Rust too many necessary things are just wrappers around C and C++ making cross-compilation and reproducible builds much harder to archive.

As for availability if CGO is ok, then calling C or C++ code from Go is not that hard. Also, there is always an option to just start C++ process if extra data copies are OK.

reply
C APIs are much more annoying to wrap in Go than in Rust because of lack of enums (important) and unions (less important).
reply
Nonsense, Rust has plenty of native libraries for HTTP and JSON.
reply
Has there been a lot of progress with ML in Rust? I don't really keep up with it because it seems like every crate ends up getting abandoned and I just gave up caring.
reply
I agree! The line early on about this being for backend services caught my attention. I love the Rust language and use it for embedded firmware and PC applications, but still use Python for web backends, because Rust doesn't have any tool sets on the tier of Django (Or Rails). It has Flask analogs, without the robust Flask ecosystem. I have less experience with Go, but would choose it over Rust for web backends, for the same reason you highlight: The library (including framework) ecosystem. I am also not the biggest Async Rust fan for the standard reasons (The rust web ecosystem is almost fully Async-required).
reply
Conversely, the Go community tends to actively shun frameworks, especially anything Rails-like, and will tell you to just use the standard library. Which is good advice, the standard library really does have everything you need. But it's also roughly on a par with what's available in Rust (though as someone said above, the Go stdlib routines have been heavily, massively, tested in production by now, and are fully mature and load-bearing).
reply
Interesting! Are Go backend building custom auth, admin, DB ORM/migrations/auto migrations, templates, email, dev server etc for each project? Or each person and org has their own toolkit they use?
reply
We tend not to use ORMs, because they're evil.

There are various libraries people use for auth, etc. But rolling your own isn't hard - Go has (e.g.) bcrypt in the standard library, so most of the heavy lifting is already done, you can write a solid auth implementation in <50 lines of code using that.

Generally Go prefers libraries to frameworks. Wrap the hard bits up into a library that can then be used widely in any implementation, rather than rolling it into a one-size-fits-all implementation that doesn't really suit anyone properly.

reply
I second that no-ORM statement. I even follow that in Java. Object mapping is fine, but I’ll write my own SQL, rather than debug obscure (to me) HQL queries.
reply
please don’t generalize. there is no “we” ..

“we” are all different and i can tell you from experience that there are also many people and teams who use go and prefer ORMs and frameworks and do not build everything from scratch …

reply
true, but there does tend to be a consensus (or has been) in the Go community around a lot of this stuff.
reply
> We tend not to use ORMs, because they're evil.

This is typical Go culture. If it is not readily available in the language or the standard library, it's evil. It's an easy cop out to explain away the gaps in the ecosystem.

Not long ago, the Go team was saying that generics are evil for that very same reason.

reply
Rust backend is like this too, albeit softer. The gaps are explained as "Why would I want that/I don't need that" instead of "evil".

My GitHub is dominated by rust projects, and I think it's the nicest overall language. But not nice enough to write bespoke solutions for problems that have had robust solutions since before I started programming! There is a basic set of functionality most web apps use, and that hasn't changed in a decade+; I don't want to re-write my own version of this, nor fight compatibility problems from (comparatively) poorly-integrated and documented libs.

I am trying to make good decisions, and am weighing "This long-standing solution does everything I need, and is easy to use and well-documented etc" vs "People on the internet are telling me I don't need it, or I can use X rust lib instead". It feels like the "We have McDonald's at home" meme.

reply
Yes.

I think few people would want to use an ORM for the stuff you use Go for, but there are things like SQLC which can generate a lot of your "dynamic DB magic" without actually being a real dependency. You can set SQLC up to run in a container in a completely isolated environment, and then use the output, but you can frankly also just maintain the SQL which frankly isn't that different than using an ORM once you've set up the automation with ridicilously strict policies.

We use Go for some of our more vital backend parts. We mainly use Python for entirely different reasons, but since we're an energy company it's nice to have a standard library that can do everything without any sort of external dependencies. It's not because we have some sort of "not invented here" fetish, it's because we have to write and maintain a literal fuckton of complaince documents for every external dependency we use and it's already a full time job for just for Python in our information security department.

reply
Most projects I see use the standard library for almost everything, but everyone leverage a few libraries here and there for one thing or the other.

It's just a different philosophy, but it's really not unlike Rails users importing Devise or Sideqik or RSpec.

reply
I tend to use whatever I perceive to be the most fitting library for each of those concerns (except ORM), but not complete frameworks.
reply
>Are Go backend building custom auth, admin, DB ORM/migrations/auto migrations, templates, email, dev server etc for each project?

lmao, basically, yes. except when you bring this up ppl think it's not a big deal / a means for self-expression. having to sort through which libraries you prefer to glue together is a kind of freedom, if you squint hard enough.

reply
For backend web dev, there are advantages. I really like Axum's use of typing:

    pub async fn dataset_stats_handler(
        Path(dataset_id): Path<String>,
        Query(verbose): Query<bool>,
    ) -> impl IntoResponse {
      ...
    }
With a route like:

    .route("/datasets/{dataset_id}/stats", get(dataset_stats_handler))
…the "dataset_id" path variable is parsed straight into the dataset_id arg, and a query string "verbose" is parsed into a boolean. Super convenient compared to Go, and you type validation along with it.

Many other things to like: The absence of context.Context, the fact that handlers can just return the response data, etc.

What I don't like: Async.

reply
go is slightly more verbose (surprise) but you can achieve the same thing using struct binding in gin:

    type DatasetStatsQuery struct {
        Verbose bool `form:"verbose"`
    }
    
    func DatasetStatsHandler(c *gin.Context) {
        datasetID := c.Param("dataset_id")
        var query DatasetStatsQuery
        if err := c.ShouldBindQuery(&query); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        // query.Verbose == bool
    }}
This is actually a great example - what happens in that Rust version when the input parsing fails? Go makes it explicit.
reply
I'm not sure if that's a great example. What kind of errors could ShouldBindQuery return?

I would assume Axum returns a bad request error for you when query parsing fails, but if you do want more control over how the error is handled, you can change the parameter type to Result<Query<bool>, QueryRejection>, and the type system itself documents precisely what errors you can match against.[0]

[0]: https://docs.rs/axum/latest/axum/extract/rejection/enum.Quer...

reply
The Go standard library has learned to interpret path variables as well:

https://go.dev/blog/routing-enhancements

reply
To be fair, it's not -quite- as useful - `req.PathValue` only returns `string` and you have to do the conversion to other types yourself which could lead to a faffy mess of code.
reply
A project I work at uses a similar pattern (similar from what I can see):

    func Login(req LoginRequest, cookies Cookies, db *sql.DB) (LoginResponse, error) {
        ...
    }

    router.HandleFunc("POST /signup", fw.Wrap(Login))
It's just a wrapper.

It also serializes/deserializes responses and handles both JSON and templates.

db is just a singleton-lifetime dependency, we often also have ctx, http.Request, http.Response, Cookie, which are request-time lifetimes.

I thought about open-sourcing it but most Golang developers seem to hate it with a passion, so I just gave up, haha.

reply
You can not use async right? Maybe not with axum but I imagine there are fully blocking frameworks for rust.
reply
Rust does not have three error systems. It has one: the Error trait. io::Error is one of many that implement it (nothing special about it). Errors defined via thiserror also implement it.

“Anyhow” just allows you to conveniently say “some Error” if you don’t care to write out an API contract specifying types of errors your function might spit out.

reply
He's not making that up; in practice, you're going to run into and need to make mental space for the idiosyncrasies of multiple error frameworks.
reply
Not sure what you mean by that. If you're consuming the API of a crate that has functions that return errors, you're not really dealing with a "framework", you're just dealing with whatever the `E` is in the `Result<T, E>`. If that `E` doesn't implement std::error::Error, I'd consider that a deficiency (even a bug) for that crate. (Yes, I know some crates want to support use in `no_std` environments; that's what features are for.)

If I care about the specific variants of error that a function can return, so I can do different things depending on what kind of error occurred, I'll read the docs and match. That's not really a "framework" thing; that's just a basic thing that anyone has to do in any language in order to consume an API. If I need to propagate the error, I'll do so (either directly, or by wrapping it in a variant of my own error type). I don't see how any of this is "framework"-y.

A crate's decision to use thiserror (or not) does not matter to me. If a crate exposes `anyhow::Error`, that's a lazy choice and bad API design, but still "works" and I generally don't need to care about it.

Or is there something else you meant when you said "error frameworks"?

reply
Just wanted to add that `Error` has been in core since `1.81` [1], meaning that even `no_std` environments can/should represent errors the same way.

[1] https://doc.rust-lang.org/core/error/trait.Error.html

reply
I guess you might have to if you need to use a library someone's written that doesn't implement the standard.

Writing primarily applications, I couldn't tell you what error handling frameworks my dependencies are using: I literally don't know, and haven't needed to know in order to display, fail, or succeed.

EDIT to add: I use anyhow for this, so I should also add "add context to an error when I fall" to the list of things I do.

reply
What's the standard? I'm not being snarky; I'm going down the thought process of how this would work in practice.

I am on team Io Error [on std rust]", somewhat arbitrarily. If I call a lib that is on Team Anyhow, or Team Custom Error Enum, I will have to do some (Straightfoward, but a little clumsy) conversions if I want ? to work. This is complicated by being able to impl From<ErrorType1> for ErrorType2 only in one direction if you don't control the other crate. (due to the orphan rule)

reply
By standard I meant an error type that implements std::error::Error.

EDIT: Which I assume all my dependencies have done, given that anyhow is able to consume all of them.

I specifically called out writing applications as my use case: my only objection to tptacek's note is the somewhat universal "in practice". The burden for designing errors for a library that others will use is higher, but that's far from the default/universal experience.

Many more people are going to consume libraries & not produce any of their own, and I think my experience is representative there.

reply
There is no team io::Error. There is only one standard: https://doc.rust-lang.org/core/error/trait.Error.html
reply
Not rust specific, and most certainly not a criticism of you - but I hate when people call a lib that errors, then just bubble that error up.

I mean the error is supposed to be tailored to the audience - I guess what you are saying is that you handle the error by saying "I called foo with X, Y, Z, and got this error back" in the logs - which your caller then also does - producing a log message of

ERROR: I called Foo with X Y and Z and got error: Die MF die

followed by

ERROR: I called Bar with X Y Z and a and got error: ERROR: I called Foo with X Y and Z and got error: Die MF die mf (still fool)

And so on and so forth.

If the counter is - don't log, that's fine, but you have to know where in the call graph that error state was reported to the logs

reply
I have tried to figure out some kind of unification between "collecting error state in a function", "logging error state", and "return error state to a parent".

I haven't found any satisfying solution to it all; collecting information for logging vs information that a caller would want... I've been meaning to investigate tracing_error to see if it brings it all together.

reply
Regardless of language - if you find a good, clear answer - blog the hang out of it - I for one have been searching for the right way to manage this, and it's not (yet) clearer - other than what I've said so far
reply
deleted
reply
You’re supposed to bubble errors up to the level that can appropriately deal with them? You don’t need to log them each step of the way.
reply
Yeah - but that's the same as my final point - you have to know who is supposed to manage the error/log - all the way up (and down) the call graph

edit: I've just finished debugging a multi system chain - FE -> SNS -> SQS -> Lambda -> DynamoDB -> Lambda -> Webhook -> My poor code

My code has multiple layers - and I was trying to find where in the very long chain of calls the data was being mangled

It turned out that there was an unlogged error, which was mismanaged by a caller - there's no shade here - the caller was handling the error how it was designed to, but by not logging that there was an error - it took a minute to understand.

reply
I was a big fan of go for a while. Though now that I have programmed more swift and rust recently, having a compiler that doesn’t protect against null pointer deferences or provide concurrency safety guarantees feels a little prehistoric.

Though go certainly did a much better job than rust on the standard library front.

reply
Standard library is something you have to maintain for all eternity, with identical API. It had been argued that some concurrency primitives like channels would have been better outside of std (for rust, to be clear). Once dependency management is solved, a small std is beneficial.
reply
> you have to maintain for all eternity, with identical API

People always tout this as a huge reason for not wanting a too big std in Rust (or "too useful" either), but IMHO that's just talking about reaching theoretical optimals, while leaving the community for years without good guidance via providing a opinionated practical and pragmatic way of doing things. Which I find to be a very unhelpful stance for a tool such as a programming language.

If a design of some std package didn't pass the test of time, and a new iteration would be beneficial, the language can leave its original API version right there, and evolve with a v2, with an improved and better thought out API after learning from the mistakes of v1.

Prime example: "hey we found that math/rand had some flaws, so here is math/rand/v2". A practical solution, and zero dramas as a result of having rand be part of std.

reply
Perhaps it would help if stdlibs were be versioned, with the chosen version declared in the project file. For existing languages, a lack of version would simply indicate the original stdlib, meaning nothing should break.

I definitely don't think stdlibs should be changed often, but it seems fairly damaging to a language when things may be added to a stdlib but never removed, no matter how broken or misconceived (see C++).

Rust is a great language, but the poor stdlib + overreliance on crates + explosion of unvetted transient dependencies makes it a hard sell for a lot of projects.

reply
I use go because of the large and useful stdlib. I rarely have to reach for an external library, and even then I only consider libraries that are very popular. If a library isn’t available, I’ll just write my own, using the stdlib. I recently used the awesome crypto library to implement an envelope encryption system, I didn’t need anything outside of the stdlib or Google’s x library (x is effectively the experimental stdlib).

Having too much external code, like npm or rust crates, seems like a nightmare for me.

reply
Doesn't Rust already have that solved via editions? If anything, that's the language that's especially well positioned here.
reply
I believe the stdlib can't be versioned like that: there's only one stdlib linked into a final artifact, you can't have two versions with differing APIs in there.
reply
There's work on edition aware name resolution so that a type with the "nice path" can change over an edition while still accessible through a longer path, but 1) it's not implemented yet, 2) it hasn't been used yet (see 1), 3) if it is ever used it should be done very sparingly (because of the bafflement that can occur if someone follows older docs in the new edition, implementation will come with efforts to mitigate these problems).
reply
deleted
reply
For me going from JVM and CLR ecosystem of programming languages into Go for backend development is a downgrade.

The language design makes sense in the context of Oberon (1987), and Limbo (1995).

Now when there are so many options finally building on top of Standard ML, and Lisp heritage, having to settle with Go feels like a downgrade.

I code since 1986, if I wanted if boilerplate error handling, or having cost as the only mechanism to declare constant values, there have been plenty of options.

reply
I don't fully get the argument about errors.

in rust say a function returns Result<T, E> so either the we get a result all an error how is that different from (int, err) in go?

do you not still need to handle the error?

in go you just return the error up to whatever the top caller is.

reply
Go funcs can return both a value and an error, or neither, it's a common gotcha. Having to check the behavior each time is no fun.

Missing error handling is checked at compile-time in Rust (lint-time in Go), and can be enabled for any struct or function (https://doc.rust-lang.org/reference/attributes/diagnostics.h...), not just `Result<T,E>`.

Returning an error to the caller in Rust can be done with a single character.

reply
In Go you can ignore the error value though, and use directly the returned value (`int` in your example). In Rust you cannot do that, you need to unwrap the Result or use the `?`
reply
If the returned value is still valid despite an error, then the function would return (u32, Option<Error>), perfectly valid rust. If the value is meaningless in case of an error then using it is incorrect code; you wouldn't want to do that in either language and rust makes that assumption explicit with unwrap. If you want a default value in case of error just use unwrap_or_default.
reply
I use Rust for web services all the time. It's a dream compared to Go (which I wrote professionally for years).

At this point, I can't imagine a scenario not to use Rust for writing a web API.

reply
What is "formal QA support"?
reply
I just hate how many dependencies you have to pull in for a typical Rust project vs Go. As far as Go being an ugly language, there are some interesting wrappers that put lipstick on that pig such as https://lisette.run/

But personally, I don’t mind Go at all. I’ve even begun to prefer it for some things. That may be Stockholm syndrome, though.

reply
Been playing with Lisette for a few weeks, so far im really liking it. Think is has some potential.
reply
Rust is easily one of the worst languages to look at
reply
The funny thing is: All Rust source code looks like an assembly syntax error.
reply
If you spend an hour skimming the Rust Book it's not really that bad
reply
It's not that bad, but when compared to C or Go, it's not something that I would like to type by hand. At least Java has IDEs which reduce the amount of verbosity you need to type. I know you get safety, but the verbosity and cargo is is a con in my opinion.
reply
Rust has autocomplete even in Sublime Text with an LSP... not quite as good as with Java, but I don't write either by hand much anymore.
reply
[flagged]
reply
> Rust lacks a uniform error type.

Not quite true. The unifying error trait is std::error::Error.

> pain when you have to pass them upward through a chain of calls

Kind of? You just make an enum with the various variants that need to be passed through and use the #[from] macro to generate the conversion code automatically.

It’s more characters than eg. A union type in Python or TypeScript, but it’s not much more.

Plus, it makes you think about your error design, which is important!

reply
thiserror and anyhow are just std::error with extra steps. Note that io::error is just a specific std::error.

The entire point in Rust is that you wrap Error impls with other Error impls, or translate one impl into another using a match. I've found this is far more flexible and verifiable than most other languages, because if you craft your error types with enough rigor, you can basically have a complete semantic backtrace without the overhead of a real backtrace.

I use thiserror a lot to help with my impls. Notably, all it does is impl Display and Error. It's not a specific other paradigm because it basically compiles out, it's just a macro.

Anyhow is perhaps the closest one to another paradigm because it allows you to discard typed information in favor of just the string messages, but it still integrates well with Errors (and is one).

reply
thiserror and anyhow are std::error::Error with fewer steps.
reply
They're external crates that either generate Error implementations (thiserror) or act as dynamic wrappers implementing Error (anyhow), so they're more than a simple hand-written implementation. But the developer experience of pulling thiserror or anyhow off the shelf can certainly be more convenient than the hand-written implementation, sure.
reply
I find Elixir's memory and threading models much more compelling than Go's for web services. There are many great libraries for Elixir as well, but if you need something else, Elixir makes rolling your own libraries very easy. I'd recommend giving Elixir a try, if you haven't already.
reply
Or gleam if you don’t fancy elixir.
reply
Go also has one glaring disadvantage. GC. Assuming you're in the cloud.

It's easy to write code that trivially eats memory. Plus any resources spent on it, are resources not spent on other cloud provider things.

reply
>I could see migrating from C or C++ or Python to Rust, for various reasons, but for web back-end work Go is a good match. I write almost entirely in Rust, but the last time I had to do something web server side in Rust, I now wish I'd used Go.

Now there is a cult of rewriting everything in Rust. System level software? Yes. Web? I prefer not to.

reply
[flagged]
reply
Praising go for how it handles errors, when it's even worse than C where the compiler at least warns you if you're ignoring return values of calls. That's a new one.
reply
Linters are available to catch you before you compile - with Go

Generally speaking there has to be a mechanism for optional handling of return values, in Go you can ignore everything (ew), you can use placeholders `_`, or you can explicitly handle things - my preference.

If you say "Well in C you have to handle the returns - I am not across C enough to comment, but I will ask you - Does C actually force you, or does it allow you to say "ok I will put some variables in to catch the returns, but I will never actually use those variables" - because that's very much the same as Go with the placeholder approach

edit: I am told the following is possible in C

trySomething(); // Assumes that the author of trySomething has not annotated the function as a `nodiscard`

(void)trySomething(); // Casts the return(s) to void, telling the compiler to ignore the non-handling

int dummy = trySomething(); // assign to a variable that's never used again

I welcome correction

reply
C, as a language, cannot bother less about you using or not using the return values, checking them, discarding them, or using them to index an array without any bounds checking. Various linters and compilers may have their opinions, expressed as warnings, but at the end of the day it's completely up to you as a developer.
reply
Same as in go, a language designed several decades later.
reply
Which is probably not really surprising, if we consider who were the original designers of Golang!
reply
Yeah - I assumed so - which makes the GP post... bizarre
reply