Hacker News new | ask | show | jobs
by jerf 3506 days ago
"Channels without panics. Channels are awesome, but Go's design of them means that you have to learn special ways to design your usage of channels so that it does not crash your program. This is asinine to me. So much type safety exists in Go, and yet it's so easy for a developer to trip over channels and crash their program."

I've solved this in my programming by finally coming to grips with the fact that channels are unidirectional, and if you want any bidirectional communication whatsoever, up to and including the client telling the source for whatever reason it has closed, even that one single bit, that must occur over another channel of its own. Clients must never close their incoming channels. This does mean that many things that Go programmers sometimes try to bash into one channel need to be two channels.

But I agree it's a problem.

4 comments

> I've solved this in my programming by finally coming to grips with the fact that channels are unidirectional, and if you want any bidirectional communication whatsoever, up to and including the client telling the source for whatever reason it has closed, even that one single bit, that must occur over another channel of its own. Clients must never close their incoming channels. This does mean that many things that Go programmers sometimes try to bash into one channel need to be two channels.

Erlang got it right (again), by sending messages to processes they can only be unidirectional.

I see Erlang and Go as duals of each other here, at least considered locally; Erlang focuses on the destination and Go focuses on the conduit of the message. Each have advantages and disadvantages. I think Erlang's approach ends up easier to use, but you lose the useful ability of channels to be multi-producer and multi-consumer.

(I'd like to see Erlang create a concept of a multi-mailbox where you can have a PID that can be picked up by a pool. Trying to create pools from within Erlang proper is quite challenging, and the runtime could do better. I acknowledge the non-trivial problems involved with clustering; I think it'd still be a big improvement even if they only work on the local node.)

The Erlang equivalent is that messages don't carry their source with them, so you must embed the source in the message if you want to send a message back. The main difference here is that Erlang doesn't offer anything that can be misunderstood as sending back a message any other way, so nobody is fooled and this never comes up as a problem once someone gets Erlang. The problem in Go is that the client end of the channel is capable of closing the channel, even though it really seriously never should.

I don't know how, but frankly i'd love to eliminate all simple panics. Nil pointers and channels seem two big culprits, offhand.

Granted, i left out nil pointer/interface panics because it seems unrealistic given how difficult it was for Rust to get rid of nil pointers. I'm not sure Go 2.0 could do it and still be considered Go.

> Granted, i left out nil pointer/interface panics because it seems unrealistic given how difficult it was for Rust to get rid of nil pointers.

That wasn't really a difficult part, it's just a basic application of generic enums.

Now Go doesn't have (userland) generics or enums, but they could have taken the same path as other languages (and the one Go is wont to take): special-case it. Which they probably can't anymore because of zero-valuing, you can't have a zero value for non-null pointers.

I agree; I want non-nillable types in Go. This despite the differences in Go that makes nil values more "valid" than they often are in other languages.

I still faintly hold out hope. Unlike many of the complaints about Go that would require fundamental restructuring, C# showed that actually can be retrofitted onto a language without breaking it.

> Unlike many of the complaints about Go that would require fundamental restructuring, C# showed that actually can be retrofitted onto a language without breaking it.

C# added nullable types. Not non-nullable types.

In Go, the concept of zero values is so fundamentally baked into the semantics that non-nil pointers can never really be added to it.

I'd just make my own uninitialized type in Go. In fact, I think I've done it. I'd guess you'd want to avoid the boilerplate.
> Granted, i left out nil pointer/interface panics because it seems unrealistic given how difficult it was for Rust to get rid of nil pointers.

That wasn't difficult at all.

Isn't that a timing race? You're relying on the notification of "I'm done" getting there before the other thread tries to send.

Write-and-panic has the advantage of being atomic. And you can catch a panic.

If you have a stoppable reader like that, maybe have it pull using a channel of channels. That is, consumer sends the producer "here is a one use unbuffered channel, please put an item in it" and then blocks waiting for the response.

I don't think panics on channels are a big problem. Mostly they are a symptom of bad architecture. In a good design the ownership of each end of the channel is exactly defined, similar like you would have to define the ownership for all resources in Rust. As soon as that's the case the owner can (on demand) close the channel and others can read from it and wait for the close/finish.

The only corner case is multi-producer channels, which either need additional synchronization or could be left open and garbage-collected.