Skip to content

proposal: spec: permit referring to a field shared by all elements of a type set #48522

Closed

Description

@beoran

What version of Go are you using (go version)?

$ go version
/tmp/golang-tip/bin/go version                                                                                                                      
go version devel go1.18-986f8ea6b4 Tue Sep 21 00:59:42 2021 +0000 linux/amd64

Does this issue reproduce with the latest release?

No, it is a generics issue, therefore tested with a recent tip only.

What operating system and processor architecture are you using (go env)?

linux/amd64


go env Output
$ go env
GO111MODULE="" 
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/me/.cache/go-build"
GOENV="/home/me/.config/go/env"            
GOEXE=""                                                                                                                                                                             GOEXPERIMENT=""                                                                                                                                                                      GOFLAGS=""                                                                                                                                                                           GOHOSTARCH="amd64"                                                                                                                                                                   GOHOSTOS="linux"                                                                                                                                                                     GOINSECURE=""                                                                                                                                                                        GOMODCACHE="/home/me/src/go/pkg/mod"                                                                                                                                           GONOPROXY="k8s.io/*"
GONOSUMDB=""                                                                                                                                                                         GOOS="linux"                                                                                                                                                                         GOPATH="/home/me/src/go"                                                                                                                                                       GOPRIVATE=""                                                                                                                                                                         GOPROXY=""                                                                                                                                 
GOROOT="/tmp/golang-tip"                                                                                                                                                             GOSUMDB="off"                                                                                                                                                                        GOTMPDIR=""                                                                                                                                                                          GOTOOLDIR="/tmp/golang-tip/pkg/tool/linux_amd64"                                                                                                                                     GOVCS=""                                                                                                                                                                             GOVERSION="devel go1.18-986f8ea6b4 Tue Sep 21 00:59:42 2021 +0000"                                                                                                                   GCCGO="gccgo" 
GOAMD64="v1"                                                                                                                                                                         AR="ar"                                                                                                                                                                              
CC="gcc"                                                                                                                                                                             CXX="g++"                                                                                                                                                                            CGO_ENABLED="1"                                                                                                                                                                      GOMOD="/home/me/src/gocrtp/go.mod"                                                                                                                                             CGO_CFLAGS="-g -O2"                                                                                                                                                                  CGO_CPPFLAGS=""                                                                                                                                                                      CGO_CXXFLAGS="-g -O2"                                                                                                                                                                CGO_FFLAGS="-g -O2"                                                                                                                                                                  CGO_LDFLAGS="-g -O2"                                                                                                                                                                 PKG_CONFIG="pkg-config"                                                                                                                                                              GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build1474680903=/tmp/go-build -gno-record-gcc-switches"

What did you do?

I tried to compile this program (crtp.go) with generics:

package main

import "fmt"

type Point struct {
	X, Y int
}

type Rect struct {
	X, Y, W, H int
}

type Elli struct {
	X, Y, W, H int
}

func GetX[P interface { Point | Rect | Elli }] (p P) int {
	return p.X
}

func main() {
	p := Point { 1, 2}
	r := Rect {2, 3, 7, 8}
	e := Elli {4, 5, 9, 10}
	fmt.Printf("X: %d %d %d\n", GetX(p), GetX(r), GetX(e))
}

with tmp/golang-tip/bin/go build

What did you expect to see?

Program compiles, runs and outputs X: 1 2 4

What did you see instead?

./crtp.go:19:11: p.X undefined (type bound for P has no method X)

All three structs in the type bound have an identical X /field/, so I think this is wrong. Of course there is no method but I don't think that matters here. I feel I should be able to use the public field X of p since p can only be one of the three Point, Rect, or Elli.

Activity

  1. beoran commented on Sep 21, 2021

    @beoran
    Author

    @gopherbot, please add label generics

  2. added
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    on Sep 21, 2021
  3. dr2chase commented on Sep 21, 2021

    @dr2chase
    Contributor

    @griesemer

    Reading "Composite types in constraints" in https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md I get the impression that this ought to work; the example there says "this doesn't work because the field types for X don't all match" which implies that if the field types did match, as they do here, then it would work.

    package main
    
    import "fmt"
    
    type hasX interface {
    	struct {
    		X, Y int
    	} | struct {
    		X, Y, H int
    	} | struct {
    		X, Y, W, H int
    	}
    }
    
    func GetX[P hasX](p *P) int {
    	return p.X
    }
    
    func main() {
    	p := struct {
    		X, Y int
    	}{1, 2}
    	r := struct {
    		X, Y, H int
    	}{2, 3, 8}
    	e := struct {
    		X, Y, W, H int
    	}{4, 5, 9, 10}
    	fmt.Printf("X: %d %d %d\n", GetX(&p), GetX(&r), GetX(&e))
    }
    
  4. griesemer commented on Sep 21, 2021

    @griesemer
    Contributor

    We don't currently support field accesses of this kind even though the proposal says that this should/could work. We may not support this for Go1.18 as it doesn't seem like an essential feature. There's a trivial work-around that uses a method:

    package main
    
    import "fmt"
    
    type Point struct {
    	X, Y int
    }
    
    func (p Point) GetX() int { return p.X }
    
    type Rect struct {
    	X, Y, W, H int
    }
    
    func (r Rect) GetX() int { return r.X }
    
    type Elli struct {
    	X, Y, W, H int
    }
    
    func (e Elli) GetX() int { return e.X }
    
    
    func GetX[P interface { Point | Rect | Elli; GetX() int }] (p P) int {
    	return p.GetX()
    }
    
    func main() {
    	p := Point { 1, 2}
    	r := Rect {2, 3, 7, 8}
    	e := Elli {4, 5, 9, 10}
    	fmt.Printf("X: %d %d %d\n", GetX(p), GetX(r), GetX(e))
    }

    Of course, then you don't need generic code in the first place because you could just use dynamic method dispatch. And maybe you should.

  5. beoran commented on Sep 21, 2021

    @beoran
    Author

    The reason I tried this out is for #48499, wherr the OP directly wants to get fields or pointers to fields from a set of similar structs. In that use case, the overhead of an accessor function and dynamic method dispatch would be not acceptable.

    So while it may not be essential, #48499, which is for use with Go language databases, goes to show that it is not academic, but actually would be very useful for existing code, in stead of the feature proposed in that issue. Furthermore it is more consistent and easier to learn to also allow it.

    If there is not enough time left to implement this for 1.18, then please consider this for 1.19.

  6. added
    NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.
    and removed
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    on Nov 17, 2021
  7. added this to the Go1.19 milestone on Nov 17, 2021
  8. Canadadry commented on Dec 30, 2021

    @Canadadry

    Open question here.

    I feel this is a bit restrictive because we have to explicitly say which struct statisfy the constraint. Like for the interface we may want something that will allow us to tell what is the constraint not who can pass. For example :

    package main
    
    import "fmt"
    
    type Point struct {
    	X, Y int
    }
    
    type Rect struct {
    	X, Y, W, H int
    }
    
    type Elli struct {
    	X, Y, W, H int
    }
    
    func GetX[P struct { X int }] (p P) int {
            // here p is known to have only the field X anything more
    	return p.X
    }
    
    func main() {
    	p := Point { 1, 2}
    	r := Rect {2, 3, 7, 8}
    	e := Elli {4, 5, 9, 10}
    	fmt.Printf("X: %d %d %d\n", GetX(p), GetX(r), GetX(e))
    }

    With this example we can pass any struct that has an int field name X, our function is not coupled with some defined type.

  9. ghostsquad commented on Feb 9, 2022

    @ghostsquad

    func GetX[P struct { X int }] (p P) int {

    possibly another way to describe this is..

    type Xer struct {
       X int
    }
    
    func GetX[P ~Xer](p P) int {
        return p.X
    }
    

    Just stopping by to say that this is exactly something that I was looking for. Basically, how can I avoid explicitly defining the accessors (via an interface), especially in the case when I fully intend on allowing both Get & Set to occur.

    I feel that gymnastics are a totally reasonable expectation when I either want only 1 of Get/Set, OR if the Get/Set needs extra explicit handling. In that case, an interface makes sense. Otherwise, it's just boilerplate.

  10. davidmdm commented on Feb 16, 2022

    @davidmdm

    I believe that this kind of generic structural typing would be extremely useful, and necessary for systems that have performance requirements that dynamic dispatch cannot handle.

    That being said the above suggestions for type sets with struct members only being structural seems to be against the grain of the type set. If I were to write the follow:

    type Named interface {
      string |  struct { Name string }
    }

    I expect the set of types to be the set that consists of string and the struct literal that Has a Name property of type string.
    Not the set of string and any struct with property Name. Adding the ~ operator does help express perhaps that we want structural typing for the struct, but it is inconsistent with what came before in 1.18 where the meaning of the ~ operator is to mean any type's who's underlying type is what follows the operator.

    Therefore, and this might be extremely unpopular, would it not make more sense to extend the constraint/interface syntax to allow for struct members?

    type Xer interface {
      X int
    }

    In the same way that type XGetter interface{ GetX() int } represents the set of types that implement the method GetX, Xer would be the set of types that have an accessor X ?

    This way we don't need to touch what the tilde operator means, or how to interpret type unions? Otherwise I think we would be taking two steps backwards in regards to eventually supporting sum types (if ever).

    EDIT:

    To add a more concrete scenario suppose:

    type Point1D { X int }
    type Point2D { X, Y int }
    type Point3D { X, Y, Z int }
    
    // type constraint
    type TwoDimensional interface { X, Y int }
    
    // Works for any struct with X and Y of type int, including Point2D and Point3D, etc.
    func SomePlanar2DOperation[T TwoDimensional](value T) { ... }
  11. beoran commented on Feb 17, 2022

    @beoran
    Author

    Many interesting ideas here. But, if the use case of my original examples could work somehow, I don't mind the implementation details.

  12. davidmdm commented on Feb 18, 2022

    @davidmdm

    I think my idea might need to be rewritten as a proposal. As far as whether common fields of a struct union should usable from the function body, I agree. But somebody who knows if it is implementable should comment.

  13. yulrizka commented on Feb 18, 2022

    @yulrizka

    I have a real scenario where I'm working on ETL jobs that work nicely with generics.
    It removes the necessity to do type checking with switch.

    The only thing that prevents it to work is assigning a value of a Field to the struct. And since there is a lot of struct object and each has a lot of properties, adding setter / getter for each struct seems to defeat the whole purpose.

  14. 94 remaining items

  15. moved this from Active to Declined in Proposalson Nov 14, 2023
  16. viccon commented on Jul 3, 2024

    @viccon

    I think it's a bit unfortunate that this issue was closed in favor of #63940 because many Go developers are probably not familiar with the concept of core types. However, 8 percent of developers who answered the latest Go developer survey felt that generics were too limited for their needs. Perhaps it would be better to leave an issue like this open to better understand the challenges people are facing rather than having an umbrella issue that seems focused on the internal implementation?

    image

  17. ianlancetaylor commented on Jul 3, 2024

    @ianlancetaylor
    Contributor

    Core types are not an aspect of the internal implementation, they are defined in the language spec. It won't help anybody to discuss the same issue in two different places.

  18. thepudds commented on Jul 3, 2024

    @thepudds
    Member

    Hi @CreativeCreature, FWIW note that #63940 opens citing this issue here as a motivation, and I don’t think this aspect will be forgotten as work (hopefully) progresses.

    From the opening comment of #63940:

    We should investigate if we can avoid the use of core types more widely. We already do avoid them for some operations, such as indexing. If we can avoid them everywhere, we should be able to enable some desired features (such as #48522) w/o extra rules but as a matter of course.

  19. gopherbot commented on Oct 29, 2024

    @gopherbot
    Contributor

    Change https://go.dev/cl/618655 mentions this issue: go/types, types2: allow access of fields shared by all elements of a type set

  20. gopherbot commented on Dec 10, 2024

    @gopherbot
    Contributor

    Change https://go.dev/cl/635046 mentions this issue: go/types, types2: allow access of fields shared by all elements of a type set

  21. bytheway commented on Mar 27, 2025

    @bytheway

    Now that core type work is done, and the official blog (https://go.dev/blog/coretypes) literally points right to this issue as an example of what that work makes possible, could we please have this issue reopened and considered? Other proposals similar to this are getting marked as duplicates to this one, but this remains closed.

    Please reopen and consider implementing shared field generic types.

  22. caozhuozi commented on Mar 29, 2025

    @caozhuozi
  23. deefdragon commented on Jan 9, 2026

    @deefdragon

    @aclements requesting this be reopened for the reasons mentioned by @bytheway, as well as my original arguments here that it should not have been closed to begin with while waiting on the other (now completed) issues.

  24. phenpessoa commented on Jan 12, 2026

    @phenpessoa

    @griesemer tagging you as well, since you are assigned to this issue.

  25. atdiar commented on Jan 12, 2026

    @atdiar

    Probably could or could wait a little bit and have a new issue that encompasses everything.

    For that one bit, we need to think about how it interacts with methods since one cannot have fields and methods of a same name.

    There are implicit additional constraints. That is going to be a problem to tackle for proper type set computations/ interface definitions. (I think it is feasible but requires adjustment)

    Maybe how it deals with composition/embedding is another issue (probably more manageable).

    Just to be clear, I've needed/wanted this feature in the past but it requires some thought I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions