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: type range #40621

Closed
Jorropo opened this issue Aug 6, 2020 · 3 comments
Closed

proposal: Go 2: type range #40621

Jorropo opened this issue Aug 6, 2020 · 3 comments
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@Jorropo
Copy link
Member

Jorropo commented Aug 6, 2020

The Problem

In go functions capable of accepting different types uses interface{}, this work well most of the time but this very unclear about what your function actualy accepts. Also dealing with case when someone pass a wrong type resolve most of the time in panic or error but idealy we want the build the to fail because the function will never work with a wrong type.

The Solution :

Allows people to define type range :

type uNumber uint8|uint16|uint32|uint64|uint

A type range is obtained by oring types, the result is a type exposing interface{} (and also more less working like it) but only capable to fit the given types :

func a(n ...uNumber) { /* Do Some Stuff */ }

func main() {
  var b uint
  var c uint8
  a(b, c) // Works

  var d int
  a(d) // Error : "type int doesn't fit in type range uint8|uint16|uint32|uint64"
}

Do achieve a similar result currently you would use interface{} :

func a(n ...interface{}) { /* Do Some Stuff */ }

func main() {
  var b uint
  var c uint8
  a(b, c) // Works

  var d int
  a(d) // Also work even this shouldn't because a doesn't support int
}

As you can see this is more ambiguous, also there is no good solution for a to solve this issue (could be returning with an error or panic but idealy we want this to not build).

How to exploit a type range types :

Earlier I've said type range expose interface{} so to use it you must cast it :

func a(n ...uNumber) {
  for _, t := range n {
    switch v := t.(type) {
    case uint8:
      // Do stuff
    case uint16:
      // Do stuff
    case uint32:
      // Do stuff
    case uint64:
      // Do stuff
    }
  }
}

This is still like how we do with interface{} but adds more security, trying to cast to a type not in type range errors :

func a(n uNumber) {
  n, ok := n.(int) // Error : "type int doesn't fit in type range uint8|uint16|uint32|uint64"
}

Other things :

Type range can be merged and can be used outside of a type declaration, if multiple types overlap the type is only added once :

func b(n uNumber|int|uint) {
  fmt.Printf("%T\n", n) // uint8|uint16|uint32|uint64|int
}

The order of types doesn't matter (it's just if a type is in or not) and may be changed by the compiler or reflection.

You should be able to create a range with any type (like interface{} does), so net.IP|uint32|[4]byte|[16]byte would be legal.

Why not using a less permissive model for what is exported ? (if every types in your type range have a String() string method having String be callable in the type range it self)
Because this is dangerous, if any one of your types change this breaks your code with a very unhelpfull error message. If you want to do this just use something like interface{String() string} for your type.

Change in the core libs ? Maybe some method like strconv.Itoa or other might be benificial but that not really what I'm up for (plus this can be done later) (plus methods have to manualy deal with type range so this might reduce performance or code readability).

Reflection ? I think this would be needed but I'm not experienced enough with reflect to propose something. I just know you should be able to test if a Type fit in a type range and extract types of a type range.

EDIT:
I forgot 2 things :
You can use a type range to an other type range if all types of the red one fit in the writed one :

var a int|uint = 6
var b uint|int|uint8 = a // Works
var c uint|uint8 = a // Errors : "int doesn't fit in uint32|uint8"

If you have an interface in your type range an object fitting the interface also fit the type range :

type s struct {}
func (b s) Foo(){}
type i interface{
  Foo()
}

var a s = s{}
var b int|i = a // Works because s fit i
@gopherbot gopherbot added this to the Proposal milestone Aug 6, 2020
@martisch martisch added v2 A language change or incompatible library change LanguageChange labels Aug 6, 2020
@mvdan
Copy link
Member

mvdan commented Aug 6, 2020

Please note that you should fill https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing a language change.

@mvdan mvdan added the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Aug 6, 2020
@ianlancetaylor
Copy link
Contributor

I think this should be folded into #19412. I think it's the same basic idea.

@ianlancetaylor ianlancetaylor changed the title Proposal: Type Range proposal: Go 2: type range Aug 6, 2020
@Jorropo
Copy link
Member Author

Jorropo commented Aug 6, 2020

Please note that you should fill https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing a language change.

  • Would you consider yourself a novice, intermediate, or experienced Go programmer?
    intermediate
  • What other languages do you have experience with?
    python, solidity, js like
  • Would this change make Go easier or harder to learn, and why?
    Way easier because this would remove most of the uncertainty about interface{} as a call params
  • Has this idea, or one like it, been proposed before?
    yes proposal: spec: add sum types / discriminated unions #19412 (my quick search didn't brough it to me, thx @ianlancetaylor )
    • If so, how does this proposal differ?
      I explore all case and should can and cant be used (while proposal: spec: add sum types / discriminated unions #19412 is more of a discussion, but in fairness you could probably extract a design doc out of it)
      Also I don't think : "it should be a value type or the types contained in them are fixed at compile-time" I really think it should works as interface{} does currently (if the reflect aspect was not here a text preprocessor could even do it (just checking the type and replacing type range by interface{} if this fit))
  • Who does this proposal help, and why?
    I do it mostly because when I want to use a lib I usualy use godoc to learn how it works and seing interface{} as argument or return is terribly unhelpfull plus there is no good thing to do to manage wrong type in your fonction.
  • What is the proposed change?
  • Is this change backward compatible?
    Technicaly all the old code will not break (or if it is it was already broken in the past but building).
    First all old code is using interface{} and there is no change to that and even if a programer choose to break interface{} in profit of a type range he will very likely put all the previous already working type as argument.
  • Show example code before and after the change.
  • What is the cost of this proposal? (Every language change has a cost).
    Lots of small change in lots of place of the code base.
    • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
      Prety much all of them
    • What is the compile time cost?
      Small (plus scales very well in multiple cores)
    • What is the run time cost?
      None (very tiny costier in reflect).
  • Can you describe a possible implementation?
    Idk, if reflect was not in question this could be just a bunch of easy check (each time a type range is encountered try to see if the type match any in the type range), but since this do have some impact on runtime idk how this could be added in the runtime.
    • Do you have a prototype? (This is not required.)
      No
  • How would the language spec change?
    New way to accept permissive types.
  • Orthogonality: how does this change interact or overlap with existing features?
    You could say it overlaps with interfaces in general but this would allows way more than an interface can do (in term of type matching).
  • Is this about generics?
    Maybe, I'm not sure.

@seankhliao seankhliao removed the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Jun 12, 2022
@Jorropo Jorropo closed this as completed Jun 15, 2022
@golang golang locked and limited conversation to collaborators Jun 15, 2023
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