Earlier quoted context omitted.
> It's exceedingly rare to see any sort of global mutable state I know a bit of Rust, so you don't need to explain in details. How to use a local cache or db connection pool in Rust (both of them, IMO, are the right use case of global mutable state)?
Why does that have to be global? You can still pass it around. If you don't want to clobber registers, you can still put it in a struct. I don't imagine you are trying to avoid the overhead of dereferencing a pointer.
Thoughts on Go vs. Rust vs. Zig
561–570 of 599 posts
Re: Thoughts on Go vs. Rust vs. Zig
#562Earlier quoted context omitted.
Well, it's far from trivial to introduce such a change if you don't want to throw away 3 decades of code/assumptions. Of course null safety would be very welcome, I'm with you on this. As for unchecked exceptions, that may be a bit of an "unreasonable ask". The only language that properly solves the problem are languages with effect types, which are an active research area. Every other language have either FP-like er…
Nah man. They could make checked exceptions less boiler platey. Simple things like Swift’s try! Or try?, and making the try construct an expression would go a long way to increase checked error usage and therefore correctness. We don’t even need to solve the lambda problem. We just need to make it easy to deal with checked exceptions. Right now you have a minimum 5 lines of boiler plate to escape them.
I believe there was a proposal to incorporate it into the switch expression? That may make it slightly too complex though, with null handling and pattern matching.
Re: Thoughts on Go vs. Rust vs. Zig
#563Earlier quoted context omitted.
FWIW `fmt.Errorf("opening file %s: %w", filePath, err)` is pretty much equivalent to calling `err.with_context(|| format!("opening file {}", path))?` with anyhow. What `thiserror` or manually implementing `Error` buys you is the ability to actually do something about higher-level errors. In Rust design, not doing so in a public facing API is indeed considered bad practice. In Go, nobody seems to care about that, whic…
> In Go, nobody seems to care about that, which of course makes code easier to write, but catching errors quickly becomes stringly typed. In Go we just use errors.Is() or errors.As() to check for specific error values or types (respectively). It’s not stringly typed. > If you wish to make sure it's not a breaking change, mark your enum as `#[non_exhaustive]`. Not terribly elegant, but that's exactly what this is for.…
errors.Is() works only if the error is a singleton.
errors.As() works only if the developer has defined their own error implementing both `Error() string` (which is part of the `error` interface) and either `Unwrap() error` or `Unwrap() error[]` (neither of which is part of the `error` interface). Implementing `Unwrap()` is annoying and not automatizable, to the point that I've never seen any third-party library doing it correctly.
So, in my experience, very quickly, to catch a specific error, you end up calling `Error()` and comparing strings. In fact, if my memory serves, that's exactly what `assert` does.
> I think the main grievance with Rust’s error handling is that, while I’m sure there is the possibility to use anyhow, thiserror, non_exhaustive, etc in various combinations to build an overall elegant error handling system, that system isn’t (last I checked) canon, and different people give different, sometimes contradictory advice.
Yeah, this is absolutely a problem in Rust. I _think_ it's moving slowly in the right direction, but I'm not holding my breath.
Re: Thoughts on Go vs. Rust vs. Zig
#564Re: Thoughts on Go vs. Rust vs. Zig
#565Earlier quoted context omitted.
FWIW `fmt.Errorf("opening file %s: %w", filePath, err)` is pretty much equivalent to calling `err.with_context(|| format!("opening file {}", path))?` with anyhow. What `thiserror` or manually implementing `Error` buys you is the ability to actually do something about higher-level errors. In Rust design, not doing so in a public facing API is indeed considered bad practice. In Go, nobody seems to care about that, whic…
> In Rust design, not doing so in a public facing API is indeed considered bad practice. In Go, nobody seems to care about that, which of course makes code easier to write, but catching errors quickly becomes stringly typed. Yes, it's possible to do it correctly in Go, but it's ridiculously complicated, and I don't think I've ever seen any third-party library do it correctly. Yea this is exactly what I'm talking abou…
In Go, `if err != nil { return nil, fmt.Errorf(...) }` is considered handling an error.
In Rust, the equivalent `.context(...)?` is considered passing an error. Handling it is about finding out what happened and doing something about it.
Re: Thoughts on Go vs. Rust vs. Zig
#566> In Go, a slice is a fat pointer to a contiguous sequence in memory, but a slice can also grow, meaning that it subsumes the functionality of Rust’s Vec type and Zig’s ArrayList. Well, not exactly. This is actually a great example of the Go philosophy of being "simple" while not being "easy". A Vec has identity; the memory underlying a Go slice does not. When you call append(), a new slice is returned that may or ma…
Writing "append(s, ...)" instead of "s = append(s, ...)" results in a compiler error because it is an unused expression. I'm not sure how a newbie could make this mistake since that code doesn't compile.
Re: Thoughts on Go vs. Rust vs. Zig
#567Earlier quoted context omitted.
> so does the rust compiler check for race conditions between threads at compile time? My understanding is that Rust prevents data races, but not all race conditions. You can still get a logical race where operations interleave in unexpected ways. Rust can’t detect that, because it’s not a memory-safety issue. So you can still get deadlocks, starvation, lost wakeups, ordering bugs, etc., but Rust gives you: - No data…
and you can have good races too (where the order doesnt matter)
Re: Thoughts on Go vs. Rust vs. Zig
#568I love this. I'm gonna steal this :)
> I’m not the first person to pick on this particular Github comment, but it perfectly illustrates the conceptual density of Rust:
The overall point here is fair, but I think it's important to clarify that (iiuc) this comment is talking about a "soundness hole". Soundness holes are cases where there's a bug in the compiler or in a library that allows someone to commit UB without writing `unsafe`. Given the goals of Rust, it doesn't matter how ungodly complicated and contrived the example is. If it produces UB without `unsafe`, then it's a bug that needs to be fixed. In practice, that means a lot of issue threads about soundness involve mind-numbing code samples that mash different features together in unintuitive ways.
But that's a good thing! No one's saying you'll ever need to look at code like this in the wild. They're saying that no matter how hard you (or your coworkers or your dependencies) try, Rust should never fail to protect memory safety in safe code.
Re: Thoughts on Go vs. Rust vs. Zig
#569Earlier quoted context omitted.
Nah man. They could make checked exceptions less boiler platey. Simple things like Swift’s try! Or try?, and making the try construct an expression would go a long way to increase checked error usage and therefore correctness. We don’t even need to solve the lambda problem. We just need to make it easy to deal with checked exceptions. Right now you have a minimum 5 lines of boiler plate to escape them.
Making it an expression would indeed by nice - I do like that in Kotlin, though it's still not particularly short there (but given that there are not really checked throws there, it's easy to just have a lambda for that). I believe there was a proposal to incorporate it into the switch expression? That may make it slightly too complex though, with null handling and pattern matching.
A a;
try {
a = someThrowingFn();
} catch (AException ex) {
throw new IllegalStateException(ex); // not possible
}
becomes var a = try! someThrowingFn();
or with Brian's proposal: var a = switch (someThrowingFn()) {
case A anA -> anA;
case throws AException ex -> throw new IllegalStateException(ex);
}
...still a bit verbose and funkyYou should check out Kotlin's proposal for error unions, I think it's pretty good and prevents a lot of boiler plate associated with results/exceptions: https://github.com/Kotlin/KEEP/blob/main/proposals/KEEP-0441.... They propose a similar construct to try! with !! like they have for nullable types.
Re: Thoughts on Go vs. Rust vs. Zig
#570Earlier quoted context omitted.
Why does that have to be global? You can still pass it around. If you don't want to clobber registers, you can still put it in a struct. I don't imagine you are trying to avoid the overhead of dereferencing a pointer.
I think a better example might be logging. How is this typically solved in Rust? Do you have to pass a Logger reference to every function that potentially wants to log something? (In C++ you would typically have functions/macros that operate on a global logger instance.)
As another comment said, global state is allowed. It just has to be proven thread-safe via Rust's Send and Sync traits, and 'static lifetime. I've used things like LazyLock and ArcSwap to achieve this in the past.