it’s 2026 and you’re not sitting in front of a teletype. There’s no need to call variables tx, rx, and especially not v.
I’m guessing 42 is just a sample value being sent. (In which case… put it in a const?) But 10 probably isn’t? Is it a capacity? I can’t tell. Give it an explicit param name.
some whitespace should make this less confusing to read
While I get your point, I would argue that there is semantic meaning in short variable names by nature. It signals 'the name of this isn't important'. And in many cases, the names of locals isn't important, and it would be nice for us to elide them entirely; names are difficult and just add confusion if used poorly.
The reference is using i for iteration or index variables. If you started naming it index or loopIndexer it would make me sit up and think there had to be something important about it - even if there wasn't.
Seeing a method full of short variable names tells me it's the kind of function where deep business logic isn't being expressed, and instead what matters is the interplay of types and other methods.
This principle can be abused, but so can all principles.
I think I once read somewhere that the bigger the scope, the more expressive naming should bec and conversely, the smaller the scope, the more terse it can be. I agree with that.
For example, inside a lambda, single-letter variables are fine. Locals should be clearer. Parameters and members clearer than that.
I never understood the big deal with variable names. I normally don't even register them other than as placeholders in my mind at this point. It's the same way with marh prrofs for me they are just arbitrary symbols.
I never understood the big deal with variable names.
You’re saving a little time writingv instead of result, but wasting your own and everyone else’s time (reviewers, future team members) subsequently reading it.
It’s the same way with marh prrofs for me they are just arbitrary symbols.
I learned on Fortran a long time ago all production code had single variable names. I think it's more of a if you didn't learn with it you don't really mind it. Sort of like newer programmers understanding how memory, stack, heap and registers work.
I learned on Fortran a long time ago all production code had single variable names.
Yes, for multiple reasons:
a lot of code carried over from how mathematicians wrote formulas
typing was harder; autocomplete was nonexistent
there was less space on displays
Sort of like newer programmers not understanding how memory, stack, heap and registers work.
There’s benefits to understanding those, but modern programming heavily abstracting them away is mostly a good thing.
Most software is “in the Czech Republic, invoices need this extra table” or “move this logo three pixels to the right”, not “this will be 3 µs faster if we save a heap allocation”.
You’re saving a little time writing v instead of result, but wasting your own and everyone else’s time (reviewers, future team members) subsequently reading it.
It is a little bit controversial. Both ways are good as:
* long names have a better chance to better explain why we need it
* short names are just shorter and easy to parse by eyes
I think go way is pretty logical. Use really short name for common patterns like loop variables. Use abbreviated variables for easy cases like r := Request{}. Use longer variables, where context is ambiguous like fooCounter. Use really long names for globals
Of course there is no best way. After all variables are for humans and it is impossible to decide which way is better without doing some scientific research on a huge number of developer writing in a different programming languages and styles
Honestly, it's just those dumb 2-letter var names that are making it seem more complicated than it is. It's ultimately just a channel send/receive, not really rocket surgery
To be fair here the Go community is really, really bad at shortening variable names unnecessarily too. Often as a consequence of casing having no semantic meaning anymore, making it harder to differentiate types from variables
Probably one of my biggest gripes. They don’t like naming conventions as if that’s a good thing.
Package names, consts, vars, globals all look exactly the same. Package names can conflict with var names I would have otherwise used. Name pollution everywhere.
I don’t know where I should expect to find anything. People put too much stuff into the same files.
They don’t like IWhatever names to designate interfaces, and there is no good way to know that something is an interface without editor support.
Go's support of interfaces in general drive me nuts. It is so much more painful to me than the bad generics.
What is an interface? What is implementing an interface? Where is the interface that it is implementing? Is my object correctly actually implementing the interface, or did I miss something so now it silently fails to match the type?
If the library does not meticulously document this, there is no way of knowing short of meticulously reading the entire codebase. What I would give for an 'implements' keyword.
But yes, it is not a good example. That code is unidiomatic in at least three different ways. In defence of the author though I would say it's hard to demonstrate such narrow functionality idiomatically without making the whole example ten times longer (example: you wouldn't unwrap, you'd handle the error elsewhere, but exactly how would depend on what that elsewhere looked like; in a microexample such as this you have nowhere else to go).
True, this is literally just how channels are in rust (but maybe actually import the libraries or use something besides mpsc) and it's being compared to a language where channels are an actual language feature (one of like 2 or 3 features in the entire language).
I would expect most programmers who understand asynchronous communication to be able to read that. Everything except move and unwrap should be pretty easy to imagine exactly what it does and you can just ignore those two and still get the basic understanding on whats going on.
Yep, and all the info here is required if you want to use channels as a library in a non-garbage collected language with memory and race condition safety. Maybe the i32 could be implied.
For me, it's a lot more about the density of information that makes it a challenge to read, rather than misinterpretation. There's a lot to unpack in each of those lines.
The code is basically the equivalent of that person who describes a basic task as if it's insight that not everyone knows but they do, some will sit there patiently letting then speak, and others don't have time for that shit. He's in camp 1 and you're in camp 2
This is a reasonable analogy, although I think in the case of unwraps, everyone would rather have the insight that it's happening, they just don't know it yet or think they know it.
Are you familiar with rust at all out of curiosity? This is indeed a lot of text to unpack, but most of it is as ubiquitously used (like, as ubiquitously as the "+" operator, I would argue even more ubiquitous in rust than "+" even).
I'm building CLEAR which is Rust - if Rust had any care in the world for being easy & intuitive instead of just being zero cost abstractions and good expressivity: https://github.com/cuzzo/clear - still in the research stage, no promise it ever gets finished as its a massive scope and a side project, but you might be interested.
This looks horrifically worse than rust, no offence. Like your first example is a bunch of raises instead of a strictly defined Result type. This is nothing to do with rust, and loses all of the ergonomics of rust.
Certainly not the primary cause: unwraps are rare in real code, you see that an error can happen and you handle it. unwrap() is fine in this illustrative example.
Note that those channel operations can fail in Go too, but you have to actively look for and handle the eventuality (and most Go code doesn't). Tokio channels tell you upfront that the operation can fail, it's a good thing.
Ignoring possible errors is a common bug in any language, Rust works hard to make it less likely.
The only things making this hard to read are the explicit imports. Otherwise it's making very simple operations, I'm not sure how more simple this could get. Create a channel, spawn passing a closure, receive, done. Await is necessary because it is async. Unwrap adds noise but that's on purpose: Rust won't let you handwave errors unless you're explicit about it.
Goroutines are now asynchronously preemptible. As a result, loops without function calls no longer potentially deadlock the scheduler or significantly delay garbage collection. This is supported on all platforms except windows/arm, darwin/arm, js/wasm, and plan9/*.
A consequence of the implementation of preemption is that on Unix systems, including Linux and macOS systems, programs built with Go 1.14 will receive more signals than programs built with earlier releases. This means that programs that use packages like syscall or golang.org/x/sys/unix will see more slow system calls fail with EINTR errors. Those programs will have to handle those errors in some way, most likely looping to try the system call again. For more information about this see man 7 signal for Linux systems or similar documentation for other systems.
Production incidents: this is the one teams are most surprised by. The classes of bugs that survive go test -race and reach production (data races, nil dereferences, missed error paths) just don’t compile in Rust. Oncall rotations are typically very boring after a Rust migration.
I find this part genuinely shocking, as those probably account for less than 1% of the bugs I've seen across our rather large number of Go components.
I see a lot of race conditions, but not the type that static analysis nor rust can help with, namely races that come from multiple systems (not shared memory). Our race detection unit tests are good enough to catch most of our shared memory race conditions and like you, I just don't find it to be that common of a source of production issues (even though we have a ton of parallelism).
Maybe it's because other areas are just so much more complicated?
The most common "simple" serious production crash/lock I have had with Go programs are due to invalid slice offset access which is something that should crash most programs in any language because it creates an unknown state if the program is allowed to continue.
nil dereferences are very uncommon and I think I have discovered all of them in development or pre production environments.
I have had some deadlocks in very complicated concurrent code though but I almost never write that kind of code in my Go programs so it's not really a problem.
Both languages are fine though. Personally I would probably pick Go over Rust for most projects unless I had specific requirements that says otherwise. There are a lot of things I like about both languages but for day to day work the faster build times with Go and the test caching really makes Go projects so nice and quick to work with.
In Go's case they were aware of the "Hoare's billion-dollar mistake" moniker, but didn't believe Tony Hoare; quoting Ian lance Taylor:
I don't personally think that permitting pointers to be nil is a
billion dollar mistake. In my C/C++ programming I've never noticed
that NULL pointers are a noticeable source of bugs.
It's also frustrating because the other popular but generally poorly designed languages skew in the direction of their creators not knowing better at the time, or thinking the language would have a very small scope. But the Go team was repeatedly led to water, and then refused to drink.
I do have some sympathy for the gophers who tell newcomers to rather use some other language if there's a feature they miss. It could've been a neat little hobby language. But given it was used for Kubernetes and then a bunch of stuff in that ecosystem, there's no real way for it to avoid the complaints that come from people who use it for a job rather than for fun.
I remember being turned off when the official language tutorial on their website was telling me to use single-letter variable names instead of "long" descriptive names.
It’s also frustrating because the other popular but generally poorly designed languages skew in the direction of their creators not knowing better at the time, or thinking the language would have a very small scope. But the Go team was repeatedly led to water, and then refused to drink.
Yep.
Some stuff Go did has inspired other languages, and I’m not generally against opinionated design, but the language has made so many bizarre choices I can’t recommend anyone start a project in it.
Can you give an example? From my perspective go is mostly C with a GC, concurrency, and a small set of language features. It doesn't have a whole lot of language innovations going for it. Nothing that hasn't been seen before go or that I think others have really stolen.
Clojure adopted the channels / goroutines. But they also made quite a bit of noise about being able to add it as a simple library while Go needed a whole new language.
Go appeared 2009, before async/await made pervasive concurrency far more common
Erlang's actor system predates goroutines by several decades. Java 1.0 had green threads (though those were later removed for just regular platform threads).
F# 2.0 added async/await in 2007. That's what a lot of languages ultimately adopted.
These concurrent concepts are all pretty well known and tried out before go.
Tooling is a fair one, though. I know rust in particular adopted tooling like rustfmt inspired by the inbuilt set of tools with go.
Erlang’s actor system predates goroutines by several decades.
It does, but Erlang is quite niche compared to Go.
F# 2.0 added async/await in 2007. That’s what a lot of languages ultimately adopted.
This is true, but I think both Go’s goroutines and F#’s async/await helped make a modern idea of concurrency more mainstream. In terms of language design, sure, C# (for obvious reasons), TS, Swift, etc. all went with async/await.
These concurrent concepts are all pretty well known and tried out before go.
Erlang pioneered things, sure. But Java’s use of green threads predated mainstream adoption of modern concurrency concepts by a decade and a half and was basically forgotten by then.
Go appeared 2009, before async/await made pervasive concurrency far more common
But Go does not have that model. It has green threads. Other languages had green threads before Go (Erlang and yes even Java had green threads at one point) but probably perf timing reasons is why it just never got picked up.
Go has good tooling for producing a small self-contained binary for many ABIs
This is largely I think why Go is picked to this day. Like you can actually pump out executables in both C# and Java as both have AOT but they don't interface easily with C libraries and seeing a 40 meg download I guess scares folks.
Like you can actually pump out executables in both C# and Java as both have AOT but they don’t interface easily with C libraries and seeing a 40 meg download I guess scares folks.
Yep. AOT .NET binaries are still quite large, unfortunately.
I could see see how it can be an issue in some kinds of code that needs to use a lot of pointers because it needs to handle the case of no value a lot.
I have typically worked in code bases where that is kept to a minimum but I also used things like the AWS SDK where almost every struct member is an pointer in a lot of request/response structs and that has also gone well.
Dev monoculture really shining here. Rust isn't the only choice for correctness and it's probably the worst choice when it comes to web backend ecosystem.
Nope, race conditions are issues related to bad design of concurrency. For example lock/unlock/lock combination instead of doing it in a single transaction, where whatever happens in between may break the assumptions
There is no mainstream language, which guards you from race conditions. Moreover they are much worse than data races, because they cannot be detected pretty quick with a -race detector or any other equivalent
fn handle(&self, req: &Request) -> Result<(), ServiceError> {
let user = self.repo.find(req.user_id)?; // returns Option<User>; ? short-circuits None into an error
user.notify()
}
This is incorrect - ? cannot be used on an Option when the function returns a Result. You need to use ok_or or ok_or_else to convert it to a Result first - which is also how you specify the exact error you want to return, because Rust won't guess for you.
That FromResidual works by taking the error inside the Err(...), into()ing it to the other error, and wrapping it with another Err(...). For that to work in None, you'd have to implement a conversion from the thing inside the None to the error type. But... there is nothing inside the None.
If Rust wanted to support it they could, for example, impl FromResidual<Option<Infallible>> for Result<T, E> where E: Default and then use the error's default value will be used when you ? a None. But Option and Result are semantically different, and automatically converting one to the other is an invitation for mistakes.
Backend services are where Go is strongest, small static binaries
SMALL?! Cries in old man
in Go you typically reach for third-party tools
In most languages? I think Elixir is the only I language I use where format/doc/install and so on is provided by the core team, and even that leans on some Erlang stuff. Maybe Dart?
Rust gives you no GC, stricter compile-time checks, and zero-cost abstractions at the cost of a steeper learning curve and slower builds.
I don't like Go much but I don't care about any of that for writing backend services unless it's a fibonacci service or something.
a code path runs where someone forgot to check whether a pointer was nil, and the goroutine panics. A common case is a lookup that returns the zero value
Well right, sometimes the reasonable thing to do is panic, eg. when a lookup that is never supposed to return 0 does in fact return 0 or the database is not reachable or another service that has to be up for mine to work is not up... 'no runtime panics' makes sense to me the more control you have over the runtime. For a video game or something, awesome, for a service depending on 12 external things I have no control over that I am only running on my own servers, go ahead and panic.
In Rust, sharing mutable state across threads
Maybe Go and Rust programmers are just more hardcore than me but for the last like 20 years I've been offloading this to application servers, I can't remember the last code I wrote that took a request and returned a response was managing its own thread synchronization, even before I found the glory of the BEAM languages.
Rust generics monomorphize, which means each instantiation produces specialized code with zero runtime cost. Combined with traits, this gives you real zero-cost abstractions.
Right, but at what cost to my sanity trying to read code that uses traits all over the place?
Rust is worth it if the cost of shipping bugs exceeds the cost of a stricter compiler.
Put me on team bugs for the use case of backend services.
The error handling segment also highlights some different approaches to references:
Go will practically necessitate a *Config, error return type because you're not returning T XOR E, you're returning T AND E, and if you'd handed off a Config, error, you'd have to construct some default Config in the case where you actually want to error out. Having it be *Config opens up the opportunity to return nil, error.
(The Config would also be at risk of being used if the error doesn't get handled properly for some reason)
* Since Rust lets you return T XOR E, you won't be returning some dummy Config, but Rust requires returning a Config rather than &Config, or else having the reference be static.
(Or some more complex lifetime stuff that wouldn't be relevant in this particular case where it's constructed from something the function reads from the filesystem, and thus clearly holds ownership of the data.)
jared__@reddit
in your example:
that to me is just hard to read and not developer friendly, imo
chucker23n@reddit
Stylistically:
tx,rx, and especially notv.42is just a sample value being sent. (In which case… put it in a const?) But 10 probably isn’t? Is it a capacity? I can’t tell. Give it an explicit param name.fiedzia@reddit
No, but you are at certain level of abstraction and there is no more meaning to put into names here.
chucker23n@reddit
The meaning
vhas for me is “I value my time writing this once more than your (and my) time reading this many times in the future”.Saint_Nitouche@reddit
While I get your point, I would argue that there is semantic meaning in short variable names by nature. It signals 'the name of this isn't important'. And in many cases, the names of locals isn't important, and it would be nice for us to elide them entirely; names are difficult and just add confusion if used poorly.
The reference is using
ifor iteration or index variables. If you started naming itindexorloopIndexerit would make me sit up and think there had to be something important about it - even if there wasn't.Seeing a method full of short variable names tells me it's the kind of function where deep business logic isn't being expressed, and instead what matters is the interplay of types and other methods.
This principle can be abused, but so can all principles.
chucker23n@reddit
I think I once read somewhere that the bigger the scope, the more expressive naming should bec and conversely, the smaller the scope, the more terse it can be. I agree with that.
For example, inside a lambda, single-letter variables are fine. Locals should be clearer. Parameters and members clearer than that.
Ok-Armadillo-5634@reddit
I never understood the big deal with variable names. I normally don't even register them other than as placeholders in my mind at this point. It's the same way with marh prrofs for me they are just arbitrary symbols.
chucker23n@reddit
You’re saving a little time writing
vinstead ofresult, but wasting your own and everyone else’s time (reviewers, future team members) subsequently reading it.But a math proof isn’t production code.
Ok-Armadillo-5634@reddit
I learned on Fortran a long time ago all production code had single variable names. I think it's more of a if you didn't learn with it you don't really mind it. Sort of like newer programmers understanding how memory, stack, heap and registers work.
chucker23n@reddit
Yes, for multiple reasons:
There’s benefits to understanding those, but modern programming heavily abstracting them away is mostly a good thing.
Most software is “in the Czech Republic, invoices need this extra table” or “move this logo three pixels to the right”, not “this will be 3 µs faster if we save a heap allocation”.
Brayneeah@reddit
And sometimes you were even just punching the cards by hand!
Revolutionary_Ad7262@reddit
It is a little bit controversial. Both ways are good as: * long names have a better chance to better explain why we need it * short names are just shorter and easy to parse by eyes
I think go way is pretty logical. Use really short name for common patterns like loop variables. Use abbreviated variables for easy cases like
r := Request{}. Use longer variables, where context is ambiguous likefooCounter. Use really long names for globalsOf course there is no best way. After all variables are for humans and it is impossible to decide which way is better without doing some scientific research on a huge number of developer writing in a different programming languages and styles
bloodwhore@reddit
Indeed.
Maybe it's as easy as to disregard if err != nil once you're used to it. But a one liner of what you posted is not readable in a quick way.
desmaraisp@reddit
Honestly, it's just those dumb 2-letter var names that are making it seem more complicated than it is. It's ultimately just a channel send/receive, not really rocket surgery
Urik88@reddit
To be fair here the Go community is really, really bad at shortening variable names unnecessarily too. Often as a consequence of casing having no semantic meaning anymore, making it harder to differentiate types from variables
Graumm@reddit
Probably one of my biggest gripes. They don’t like naming conventions as if that’s a good thing.
Package names, consts, vars, globals all look exactly the same. Package names can conflict with var names I would have otherwise used. Name pollution everywhere.
I don’t know where I should expect to find anything. People put too much stuff into the same files.
They don’t like IWhatever names to designate interfaces, and there is no good way to know that something is an interface without editor support.
saynay@reddit
Go's support of interfaces in general drive me nuts. It is so much more painful to me than the bad generics.
What is an interface? What is implementing an interface? Where is the interface that it is implementing? Is my object correctly actually implementing the interface, or did I miss something so now it silently fails to match the type?
If the library does not meticulously document this, there is no way of knowing short of meticulously reading the entire codebase. What I would give for an 'implements' keyword.
Urik88@reddit
An easy way to know how to implement an interface is:
var _ InterfaceType = MyActualType{}Then the compiler will yell at you if it doesn't implement it and you'll leave a record of what your type implements.
Graumm@reddit
Right you get interface errors at the call site, and not the file that needs to be fixed.
txdv@reddit
Everything is not exactly rocket science except for maybe rocket science.
fill-me-up-scotty@reddit
Well it's not exactly brain surgery is it?
Ok-Armadillo-5634@reddit
for me its the five :: in a row
zed_three@reddit
that could easily be fixed by changing the
importsyklemil@reddit
Yeah, moving everything into imports (and dropping the voluntary type signature), a compiling¹ example looks like
¹ warnings about dead code and unused variables notwithstanding
Thelmholtz@reddit
It's old school telecom names for transmitter and receiver.
moltonel@reddit
tx,rxfor channels is as idiomatic asifor a loop index though, it's highly recognizable and there's little reason to be more verbose.I also find that code very straightforward, but putting my gopher hat on:
unwrap()s do look like noise. But they're arguably more developer-friendly than Go channel's silent failures.mut: looks like noise but I really wish Go had it.::<32>is nobody's favourite. But it's usually inferred, it's only in this example for illustration purpose.async,await. That makes it less flexible, but it's a good compromise that makes perfect sense for Go.moveeither, because it uses a GC. Not going to open that can of worms here.jared__@reddit
i understand it is a simple channel send/receive. my issue is that the 'simple' case looks like that.
hu6Bi5To@reddit
You've made it worse by losing the line endings.
But yes, it is not a good example. That code is unidiomatic in at least three different ways. In defence of the author though I would say it's hard to demonstrate such narrow functionality idiomatically without making the whole example ten times longer (example: you wouldn't
unwrap, you'd handle the error elsewhere, but exactly how would depend on what that elsewhere looked like; in a microexample such as this you have nowhere else to go).thomasfr@reddit
It looks pretty straight forward to me
esssential@reddit
True, this is literally just how channels are in rust (but maybe actually import the libraries or use something besides mpsc) and it's being compared to a language where channels are an actual language feature (one of like 2 or 3 features in the entire language).
thomasfr@reddit
I would expect most programmers who understand asynchronous communication to be able to read that. Everything except
moveandunwrapshould be pretty easy to imagine exactly what it does and you can just ignore those two and still get the basic understanding on whats going on.esssential@reddit
Yep, and all the info here is required if you want to use channels as a library in a non-garbage collected language with memory and race condition safety. Maybe the i32 could be implied.
yawara25@reddit
Do you happen to be from the galaxy Andromeda?
thomasfr@reddit
Exactly how would someone misinterpret that code?
It all looks very basic three step me:
yawara25@reddit
For me, it's a lot more about the density of information that makes it a challenge to read, rather than misinterpretation. There's a lot to unpack in each of those lines.
haro0828@reddit
The code is basically the equivalent of that person who describes a basic task as if it's insight that not everyone knows but they do, some will sit there patiently letting then speak, and others don't have time for that shit. He's in camp 1 and you're in camp 2
Chroiche@reddit
This is a reasonable analogy, although I think in the case of unwraps, everyone would rather have the insight that it's happening, they just don't know it yet or think they know it.
Chroiche@reddit
Are you familiar with rust at all out of curiosity? This is indeed a lot of text to unpack, but most of it is as ubiquitously used (like, as ubiquitously as the "+" operator, I would argue even more ubiquitous in rust than "+" even).
DeRay8o4@reddit
Fersure
onlyrealcuzzo@reddit
I'm building CLEAR which is Rust - if Rust had any care in the world for being easy & intuitive instead of just being zero cost abstractions and good expressivity: https://github.com/cuzzo/clear - still in the research stage, no promise it ever gets finished as its a massive scope and a side project, but you might be interested.
Chroiche@reddit
This looks horrifically worse than rust, no offence. Like your first example is a bunch of raises instead of a strictly defined Result type. This is nothing to do with rust, and loses all of the ergonomics of rust.
dvhh@reddit
isn't unwrap the primary cause of stopping bug in Rust codebase?
moltonel@reddit
Certainly not the primary cause: unwraps are rare in real code, you see that an error can happen and you handle it.
unwrap()is fine in this illustrative example.Note that those channel operations can fail in Go too, but you have to actively look for and handle the eventuality (and most Go code doesn't). Tokio channels tell you upfront that the operation can fail, it's a good thing.
Ignoring possible errors is a common bug in any language, Rust works hard to make it less likely.
UltraPoci@reddit
The only things making this hard to read are the explicit imports. Otherwise it's making very simple operations, I'm not sure how more simple this could get. Create a channel, spawn passing a closure, receive, done. Await is necessary because it is async. Unwrap adds noise but that's on purpose: Rust won't let you handwave errors unless you're explicit about it.
txdv@reddit
I think this changed since 1.14:
UloPe@reddit
I mean who’s using that, right?
txdv@reddit
Apple’s 32-bit ARM devices were mainly older iPhone, iPad, iPod touch, Apple TV, and early Apple Watch models. Modern Apple devices are 64-bit ARM.
Brayneeah@reddit
At first, I was going to point out macs, before realising that this only applies to 32-bit arm \:P
txdv@reddit
no production load unless you host on your mac mini
somebodddy@reddit
Note that there is also a
spawn_localversion that does not have that limitation. The tradeoff is thatspawn_localtasks cannot move between threads.nagai@reddit
I find this part genuinely shocking, as those probably account for less than 1% of the bugs I've seen across our rather large number of Go components.
ACoderGirl@reddit
I see a lot of race conditions, but not the type that static analysis nor rust can help with, namely races that come from multiple systems (not shared memory). Our race detection unit tests are good enough to catch most of our shared memory race conditions and like you, I just don't find it to be that common of a source of production issues (even though we have a ton of parallelism).
Maybe it's because other areas are just so much more complicated?
thomasfr@reddit
The most common "simple" serious production crash/lock I have had with Go programs are due to invalid slice offset access which is something that should crash most programs in any language because it creates an unknown state if the program is allowed to continue.
nil dereferences are very uncommon and I think I have discovered all of them in development or pre production environments.
I have had some deadlocks in very complicated concurrent code though but I almost never write that kind of code in my Go programs so it's not really a problem.
Both languages are fine though. Personally I would probably pick Go over Rust for most projects unless I had specific requirements that says otherwise. There are a lot of things I like about both languages but for day to day work the faster build times with Go and the test caching really makes Go projects so nice and quick to work with.
Thelmholtz@reddit
I've unfortunately seen a lot of nil dereferences in Go code, mostly due to not new typing validated or calculated data from raw data.
Nil is really a cow that keeps on giving and my main reason to prefer Rust whenever I can reasonably take that choice.
If we could count on people doing good design, it wouldn't be a problem, but in my experience I can't even count on myself.
syklemil@reddit
In Go's case they were aware of the "Hoare's billion-dollar mistake" moniker, but didn't believe Tony Hoare; quoting Ian lance Taylor:
jug6ernaut@reddit
It’s such a head in sand response and goes against basically all of established history.
chucker23n@reddit
That’s Go for ya
syklemil@reddit
It's also frustrating because the other popular but generally poorly designed languages skew in the direction of their creators not knowing better at the time, or thinking the language would have a very small scope. But the Go team was repeatedly led to water, and then refused to drink.
See also Pike's attitude towards types in general, and then generics and iterators, which they wound up bolting onto the language later anyway.
I do have some sympathy for the gophers who tell newcomers to rather use some other language if there's a feature they miss. It could've been a neat little hobby language. But given it was used for Kubernetes and then a bunch of stuff in that ecosystem, there's no real way for it to avoid the complaints that come from people who use it for a job rather than for fun.
Ecksters@reddit
I remember being turned off when the official language tutorial on their website was telling me to use single-letter variable names instead of "long" descriptive names.
chucker23n@reddit
Yep.
Some stuff Go did has inspired other languages, and I’m not generally against opinionated design, but the language has made so many bizarre choices I can’t recommend anyone start a project in it.
cogman10@reddit
Can you give an example? From my perspective go is mostly C with a GC, concurrency, and a small set of language features. It doesn't have a whole lot of language innovations going for it. Nothing that hasn't been seen before go or that I think others have really stolen.
chat-lu@reddit
Clojure adopted the channels / goroutines. But they also made quite a bit of noise about being able to add it as a simple library while Go needed a whole new language.
chucker23n@reddit
Both of those have gotten less important over time, as other languages have been created (Rust, Swift, Zig) or have caught up (C#).
cogman10@reddit
Erlang's actor system predates goroutines by several decades. Java 1.0 had green threads (though those were later removed for just regular platform threads).
F# 2.0 added async/await in 2007. That's what a lot of languages ultimately adopted.
These concurrent concepts are all pretty well known and tried out before go.
Tooling is a fair one, though. I know rust in particular adopted tooling like rustfmt inspired by the inbuilt set of tools with go.
chucker23n@reddit
It does, but Erlang is quite niche compared to Go.
This is true, but I think both Go’s goroutines and F#’s async/await helped make a modern idea of concurrency more mainstream. In terms of language design, sure, C# (for obvious reasons), TS, Swift, etc. all went with async/await.
Erlang pioneered things, sure. But Java’s use of green threads predated mainstream adoption of modern concurrency concepts by a decade and a half and was basically forgotten by then.
agentoutlier@reddit
But Go does not have that model. It has green threads. Other languages had green threads before Go (Erlang and yes even Java had green threads at one point) but probably perf timing reasons is why it just never got picked up.
This is largely I think why Go is picked to this day. Like you can actually pump out executables in both C# and Java as both have AOT but they don't interface easily with C libraries and seeing a 40 meg download I guess scares folks.
chucker23n@reddit
Yep. AOT .NET binaries are still quite large, unfortunately.
pjmlp@reddit
Typical Go design approach, seen multiple times.
thomasfr@reddit
I could see see how it can be an issue in some kinds of code that needs to use a lot of pointers because it needs to handle the case of no value a lot.
I have typically worked in code bases where that is kept to a minimum but I also used things like the AWS SDK where almost every struct member is an pointer in a lot of request/response structs and that has also gone well.
vips7L@reddit
Dev monoculture really shining here. Rust isn't the only choice for correctness and it's probably the worst choice when it comes to web backend ecosystem.
Saint_Nitouche@reddit
Please make your comment useful by listing the other choices.
vips7L@reddit
Please go fuck yourself.
Revolutionary_Ad7262@reddit
Nope, race conditions are issues related to bad design of concurrency. For example lock/unlock/lock combination instead of doing it in a single transaction, where whatever happens in between may break the assumptions
There is no mainstream language, which guards you from race conditions. Moreover they are much worse than data races, because they cannot be detected pretty quick with a
-racedetector or any other equivalentEntroperZero@reddit
And the idea of tracking ownership is that the compiler can point this out, no?
somebodddy@reddit
This is incorrect -
?cannot be used on anOptionwhen the function returns aResult. You need to useok_ororok_or_elseto convert it to aResultfirst - which is also how you specify the exact error you want to return, because Rust won't guess for you.XLNBot@reddit
Oh, nice to know. I thought he just implemented some kind of From trait for his ServiceError so that it automatically converts a None into it.
somebodddy@reddit
Pretty sure that's not doable in Rust.
You can implement conversion from one error type to another because the
Resulttype supports it with aFromResidualimplementation - but that's not implemented forOption->Resulteven if you implement some conversion from theNoneto the error type.Because...
Noneitself is not really a type.That
FromResidualworks by taking the error inside theErr(...),into()ing it to the other error, and wrapping it with anotherErr(...). For that to work inNone, you'd have to implement a conversion from the thing inside theNoneto the error type. But... there is nothing inside theNone.If Rust wanted to support it they could, for example,
impl FromResidual<Option<Infallible>> for Result<T, E> where E: Defaultand then use the error's default value will be used when you?aNone. ButOptionandResultare semantically different, and automatically converting one to the other is an invitation for mistakes.beb0@reddit
Glad to read this thought the AI was gaslighting me
masklinn@reddit
Yes. The article is full of such errors and dubious claims.
sisyphus@reddit
SMALL?! Cries in old man
In most languages? I think Elixir is the only I language I use where format/doc/install and so on is provided by the core team, and even that leans on some Erlang stuff. Maybe Dart?
I don't like Go much but I don't care about any of that for writing backend services unless it's a fibonacci service or something.
Well right, sometimes the reasonable thing to do is panic, eg. when a lookup that is never supposed to return 0 does in fact return 0 or the database is not reachable or another service that has to be up for mine to work is not up... 'no runtime panics' makes sense to me the more control you have over the runtime. For a video game or something, awesome, for a service depending on 12 external things I have no control over that I am only running on my own servers, go ahead and panic.
Maybe Go and Rust programmers are just more hardcore than me but for the last like 20 years I've been offloading this to application servers, I can't remember the last code I wrote that took a request and returned a response was managing its own thread synchronization, even before I found the glory of the BEAM languages.
Right, but at what cost to my sanity trying to read code that uses traits all over the place?
Put me on team bugs for the use case of backend services.
syklemil@reddit
The error handling segment also highlights some different approaches to references:
*Config, errorreturn type because you're not returningT XOR E, you're returningT AND E, and if you'd handed off aConfig, error, you'd have to construct some defaultConfigin the case where you actually want to error out. Having it be*Configopens up the opportunity to returnnil, error.(The
Configwould also be at risk of being used if the error doesn't get handled properly for some reason) * Since Rust lets you returnT XOR E, you won't be returning some dummyConfig, but Rust requires returning aConfigrather than&Config, or else having the reference be static.(Or some more complex lifetime stuff that wouldn't be relevant in this particular case where it's constructed from something the function reads from the filesystem, and thus clearly holds ownership of the data.)