Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2: nonblocking channel operators #43209

Closed
kazzmir opened this issue Dec 16, 2020 · 8 comments
Closed

proposal: Go 2: nonblocking channel operators #43209

kazzmir opened this issue Dec 16, 2020 · 8 comments
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@kazzmir
Copy link

kazzmir commented Dec 16, 2020

Proposal:

Add a short syntax for non-blocking channel operations. Something like

x <-? 2 // emit may fail
y = <-? x // receive may fail, y does not change value

Currently I achieve this with the following

select {
  case x <- 2:
  default:
}

and

select {
  case y = <-x:
  default:
}

This is similar in spirit to another proposal for safely extracting fields from an object: #42847, but maybe this proposal will have better luck :). If there is another proposal on a similar idea to the one being proposed here please close this one. This proposal is a small quality of life improvement.

Rationale:

Using channels without taking care of the possibility that they may block has a high chance of causing the entire program to become blocked due to a number of issues such as cycles between go routines that use channels to communicate and other traditional blocking behaviors (like reading user input) that prevent go routines from making progress.

To deal with these issues I mostly use buffered channels with channel operations that are allowed to fail: either because the channel is empty so receive fails, or because the channel is full so emit fails. In the situations where channel operations are allowed to fail my code is designed to either retry the operation at a later time or to just give up and wait for the next channel operation. For example, streaming a series of user inputs would drop some input if the system becomes severely overloaded.

I write the select { case x <-y: default: } code phrase so often I would appreciate having syntactic sugar for it, and maybe others would too.

Language template:

  • Would you consider yourself a novice, intermediate, or experienced Go programmer?
    Experienced

  • What other languages do you have experience with?
    C, C++, Java, Ruby, Swift, Scheme, Python, a sprinkling of Rust and Haskell

  • Would this change make Go easier or harder to learn, and why?
    I think it would be easier to explain the semantics of the <-? operator and how it expands to the larger select{ case ... } than the other way.

  • Has this idea, or one like it, been proposed before?
    I'm not sure.

  • Who does this proposal help, and why?
    Go programmers that use buffered channels extensively, because it will reduce some syntactic effort needed.

  • Please describe as precisely as possible the change to the language.
    I believe x <-? y can be correctly translated into

select {
  case x <- y:
  default:
}

and y = <-? x into

select {
  case y = <-x:
  default:
}
  • What would change in the language spec?
    If this proposal has merit I can dive deeper into this question but likely at a minimum adding a lexeme for <-? and a definition of the <-? operator in the channel part of the spec.

  • Please also describe the change informally, as in a class teaching Go.
    This change allows a programmer to specify that a channel operation will not block in a succinct way.

  • Is this change backward compatible?
    Yes

  • Show example code before and after the change.
    Before:

select {
  case x = <-y
  default:
}

After

x = <-? y
  • What is the cost of this proposal? (Every language change has a cost).
    The addition of another operator, and possibly the mental overhead of knowing the translation from the <-? operator into the more general select { ... }

  • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
    gofmt would need minimal changes if the AST changes at all, vet might possibly need some changes.

  • What is the compile time cost?
    A local AST transformation, which is very low cost.

  • What is the run time cost?
    0

  • Can you describe a possible implementation?
    Modify the lexer and parser -- should be straight forward.

  • Do you have a prototype? (This is not required.)
    No

  • How would the language spec change?
    https://golang.org/ref/spec#Receive_operator and https://golang.org/ref/spec#Send_statements would need to account for the <-? operator

  • Orthogonality: how does this change interact or overlap with existing features?
    It reuses the existing select feature.

  • Is the goal of this change a performance improvement?
    No

  • Does this affect error handling?
    No.

  • Is this about generics?
    No

@gopherbot gopherbot added this to the Proposal milestone Dec 16, 2020
@mvdan
Copy link
Member

mvdan commented Dec 16, 2020

I can't say I agree with this proposal; the new syntax seems a bit cryptic, and people would have to know both ways to write the same behavior. Worse even, projects would have to choose which they prefer, and then consistently use that. I think it's best if, in general, there's just one way to write a simple operation in Go.

I think syntactic sugar is only really warranted for very common patterns, like error handling or being able to add an "init" statement to if lines. I'm not convinced this case meets that bar.

@randall77

This comment has been minimized.

@kazzmir

This comment has been minimized.

@kazzmir
Copy link
Author

kazzmir commented Dec 16, 2020

I think it's best if, in general, there's just one way to write a simple operation in Go.

The rest of what you say is reasonable, but there are already two ways of doing blocking channel operations: inside and outside of select. You could imagine that go didn't come with a way to do blocking operations outside of select, but presumably channels were such an important part of the design of go that having a syntax to do channel operations outside of select was deemed useful. Nonblocking operations could get the same treatment.

It could be said that blocking operations are the common case and deserve their own syntax, but in my experience nonblocking operations is more common. I am curious as to how others deal with blocking channel operations as well.

There are some papers trying to deal with the problem of deadlocked programs due to channels http://mrg.doc.ic.ac.uk/publications/a-static-verification-framework-for-message-passing-in-go-using-behavioural-types/draft.pdf

@DeedleFake
Copy link

DeedleFake commented Dec 16, 2020

This actually used to be a thing, way back before Go 1. If I remember right, a two-variable receive assignment, like v, ok := <-c, used to not check if it was closed, but instead check if there was something there to receive. There was a separate closed() function that checked if something was closed, but it was kind of awkward to use and put people into an incorrect frame of mind about how closing channels works. Eventually, it was separately decided to remove closed(), use v, ok := <-c to check if it was closed instead, and use select with a default case to do non-blocking receives and sends. See the patch notes for the select change and the closed() change.

All of that being said, if this is really something that you want to abstract away because, admittedly, it takes four lines to do a relatively straightforward operation, I think that generics is probably a much better way to do it:

package chans

func TryRecv[T any](c <-chan T) (v T, ok bool) {
  select {
  case v = <-c:
    ok = true
  default:
  }
  return v, ok
}

func TrySend[T any](c chan<- T, v T) (ok bool) {
  select {
  case c <- v:
    ok = true
  default:
  }
  return ok
}

@ianlancetaylor ianlancetaylor changed the title proposal: nonblocking channel operations proposal: Go 2: nonblocking channel operators Dec 16, 2020
@ianlancetaylor ianlancetaylor added v2 A language change or incompatible library change LanguageChange labels Dec 16, 2020
@ianlancetaylor
Copy link
Contributor

I agree with @DeedleFake that we should see if we can do with generics, if generics get added to the language. If we can, no need for the additional syntax.

@kazzmir
Copy link
Author

kazzmir commented Dec 16, 2020

The generics solution is a reasonable compromise, thanks!

@ianlancetaylor
Copy link
Contributor

OK, closing this issue. Thanks.

@golang golang locked and limited conversation to collaborators Dec 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

6 participants