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: spec: add nothing type for functions that don't return #69591

Closed
1 of 4 tasks
DeedleFake opened this issue Sep 23, 2024 · 8 comments
Closed
1 of 4 tasks

proposal: spec: add nothing type for functions that don't return #69591

DeedleFake opened this issue Sep 23, 2024 · 8 comments
Labels
LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal Proposal-FinalCommentPeriod
Milestone

Comments

@DeedleFake
Copy link

Go Programming Experience

Experienced

Other Languages Experience

Go, Ruby, JavaScript, C, Python, Elixir, Kotlin, Dart

Related Idea

  • Has this idea, or one like it, been proposed before?
  • Does this affect error handling?
  • Is this about generics?
  • Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit

Has this idea, or one like it, been proposed before?

As far as I know, no.

Does this affect error handling?

Not directly, but it can help with certain existing error handling patterns.

Is this about generics?

No.

Proposal

I propose adding a new builtin nothing type, nothing would only be valid to use as a return type of a function and would indicate that the function never returns because every code path in the function either stays in an infinite loop forever, panics, or calls a function that returns nothing.

This would primarily be helpful for writing helper functions for code that passes data around via panic and recover. For example, in a recent project of mine I used this mechanism in a recursive descent parser to get unrecoverable errors back up to the top of the recursion without needing to return and check them at every step of the recursion. To help with this, I wrote a number of methods along the lines of func (p *parser) raiseUnexpectedToken(tok scanner.Token) that simply construct the correct error type and then panic it, and that panic is then recovered at the top of the parser's recursion. However when I use those methods instead of panicking directly, which is more error prone as passing the wrong thing will prevent the recover from catching It, Go can't tell that the code won't continue after the call site. This leads to me having to put dummy return statements after every call to any of those helper functions. Not a huge deal, but certainly an annoyance, and it makes reading the code more confusing as it looks like the code returns there.

A nothing builtin would also have the benefit of being able to enforce that the function actually doesn't return, thus making the implementation of functions for that purpose safer.

Example

func raiseNegative() nothing {
  panic(errors.New("negative"))
}

func example(v int) int {
  switch {
  case v < 0:
    raiseNegative()
    // Without nothing, this line would have to return something.
  default:
    return v
  }
}

Reflect

reflect is the biggest complication that I can think of with this. For example, code like

reflect.New(reflect.TypeOf(func() nothing { panic(nil) }).Out(0))

would have to do something, despite it not being legal to do new(nothing) in regular Go code. Simply panicking is probably the simplest approach.

nothing might also need its own reflect.Kind.

Bikeshedding

I'm very much not stuck on nothing as the name. Some alternatives that I also kind of like are noreturn and panics.

Language Spec Changes

No response

Informal Change

The nothing predeclared identifier can be used as the sole return type of a function to indicate that it never returns. If a function is marked with this, every code path in the function must either loop forever, panic, or call a function that also returns nothing.

Is this change backward compatible?

Yes.

Orthogonality: How does this change interact or overlap with existing features?

It fits well with panic().

Would this change make Go easier or harder to learn, and why?

Mildly harder, perhaps. It's not a very complicated feature.

Cost Description

  • A new predeclared identifier.
  • A new reflect.Kind, potentially.
  • Slight increase in complexity to type-checking to handle the various cases that nothing is for.

Changes to Go ToolChain

Anything that parsed Go code would be affected.

Performance Costs

Very minor increase to compile-time cost. No increase at all to runtime.

Prototype

No response

@DeedleFake DeedleFake added LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal labels Sep 23, 2024
@gopherbot gopherbot added this to the Proposal milestone Sep 23, 2024
@ianlancetaylor
Copy link
Contributor

Every language change is a cost/benefit decision. This proposal has a cost of adding a new keyword and making the type system more complicated. The benefit is that certain kinds of code can skip writing some return statements. Personally I don't think the cost is worth the benefit.

@zephyrtronium
Copy link
Contributor

Typically, the benefit of an uninhabited type is that the type checker verifies that a function doesn't return, rather than skipping returns. It's particularly helpful with functions involving non-trivial control flow that need to guarantee there is no path to the end of the function, due to something like a missing default in a switch or a misplaced break in a loop. Today, one could approximate that goal e.g. by using type nothing struct{} as a return type and then not returning. The main benefit of a built-in nothing type over that would be totally preventing return nothing{}.

However, an uninhabited type in Go would have a great many problems. It would not be sufficient to treat nothing as just another predeclared type like bool or string. Any expression that produces a zero value must be illegal when nothing is involved (which historically has prevented a commonly desired approach to #19412).

This becomes especially complicated when type parameters are involved. Can nothing satisfy an any constraint? If so, then code constrained on any can no longer produce zero values of any of its type parameters. But all types can instantiate any type parameters, meaning that nothing is even more of a special case. https://counterexamples.org/glossary.html#empty-types contains more examples of soundness issues in programming languages with uninhabited types.

@DeedleFake
Copy link
Author

DeedleFake commented Sep 24, 2024

@zephyrtronium

There's an alternative that might avoid a bunch of those problems: Don't use a real type. Instead, use a special syntax that indicates that the function never returns. This is what Rust does, i.e. fn example() -> ! {}. That way it becomes part of the function type itself instead of something tacked onto the general type system that's only usable in a single place. That would prevent the complications with zero values, generics, and reflect.

In terms of reflect, a new method could be added, such as Type.NeverReturns(), that returns a boolean to indicate that element for function types.

@seankhliao
Copy link
Member

to me this looks like #30582 but awkwardly in the type system rather than as a compiler hint. awkwardly because it relies on possible code paths rather than just types.

@ianlancetaylor
Copy link
Contributor

Based on the discussion above, and the emoji voting on #69591 (comment), the benefit of this change does not seem to be worth the cost. Therefore, this is a likely decline. Leaving open for four weeks for final comments.

@earthboundkid
Copy link
Contributor

func raiseNegative() nothing {
  panic(errors.New("negative"))
}

func example(v int) int {
  switch {
  case v < 0:
    raiseNegative()
    // Without nothing, this line would have to return something.
  default:
    return v
  }
}

You can also write panic("unreachable") to satisfy the compiler.

@ianlancetaylor
Copy link
Contributor

No change in consensus.

@ianlancetaylor ianlancetaylor closed this as not planned Won't fix, can't repro, duplicate, stale Nov 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal Proposal-FinalCommentPeriod
Projects
None yet
Development

No branches or pull requests

7 participants