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

cmd/compile: embedded field type cannot be a (pointer to a) type parameter #49030

Closed
Fullstop000 opened this issue Oct 18, 2021 · 9 comments
Closed

Comments

@Fullstop000
Copy link

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

go version devel go1.18-cf51fb5d68 Sun Oct 17 04:27:13 2021 +0000 darwin/amd64

In https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md, the following code should be ok to be compiled:

type Lockable[T any] struct {
	T
	mu sync.Mutex
}

// Get returns the value stored in a Lockable.
func (l *Lockable[T]) Get() T {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.T
}

// Set sets the value in a Lockable.
func (l *Lockable[T]) Set(v T) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.T = v
}

What did you expect to see?

compile success

What did you see instead?

~/dev/go2playground ~/goroot/bin/go test
# go2playground [go2playground.test]
./main_test.go:50:2: embedded field type cannot be a (pointer to a) type parameter
@randall77
Copy link
Contributor

@cherrymui cherrymui added NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Oct 18, 2021
@cherrymui cherrymui added this to the Go1.18 milestone Oct 18, 2021
@cherrymui cherrymui changed the title embedded field type cannot be a (pointer to a) type parameter cmd/compile: embedded field type cannot be a (pointer to a) type parameter Oct 18, 2021
@griesemer
Copy link
Contributor

We tentatively concluded that embedding type parameters may not be permitted initially because of questions re: method promotion. The same questions arise with a pointer to a type parameter. But perhaps we can allow this after all.

Will reconsider.

@griesemer
Copy link
Contributor

We've talked about this again, and we're not going to change this for 1.18. Reasons:

  • There are open questions with respect to what the type of such an embedded field would be. Consider also the case:
func f[P constraint]() {
   type S struct{
      P // <<< should this be permitted?
   }
   ...
}
  • There are open questions with respect to method promotion.
  • We can always add it later, but we won't be able to remove the feature if we make a mistake.
  • The implementation is already restricted; making changes such as this shortly before the code freeze is risky.

The fact that the generics proposal still describes this possibility is simply an oversight. Eventually the spec (in progress) will be the final guiding document.

Closing. Once we have gained more experience with type parameters we can reconsider this as a proposal.

@griesemer griesemer removed the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Oct 29, 2021
@griesemer
Copy link
Contributor

cc: @ianlancetaylor

@szmcdull
Copy link

Sad it is not supported:

type (
	Number interface {
		constraints.Integer | constraints.Float
	}

	IntFloat[T Number] struct {
		// embedded field type cannot be a (pointer to a) type parameter
		// https://github.com/golang/go/issues/49030
		T
	}

	INumber interface {
		Add(other INumber) INumber
		Sub(other INumber) INumber
		Mul(other INumber) INumber
		Div(other INumber) INumber
		Comp(other INumber) int
		Eq(other INumber) bool
	}
)

func (me IntFloat[T]) Add(other IntFloat[T]) IntFloat[T] {
	return me.T + other.T
}

@dotaheor
Copy link

To simplify this problem, it would be better to allow embedding any type names: #24062

@gucio321
Copy link

Are there any plans for that today?

@g-talbot
Copy link

g-talbot commented Oct 8, 2023

The design doc says this should work: https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#embedded-type-parameter

But we checked in a CL to forbid it: https://go-review.googlesource.com/c/go/+/337353

@griesemer

@Fullstop000 You can always name the field, as was done here: https://go-review.googlesource.com/c/go/+/337353/5/test/typeparam/lockable.go

@randall77 @griesemer I came across this when having a similar problem, but the suggestion you give does not allow items of generic type embedded as fields to have methods that allow the embedded field to be modified. When you name the field, you cannot have methods on that type with pointer receiver. This means that you cannot write the following (toy example):

// You can edit this code!
// Click here and start typing.
package main

import (
	"fmt"
	"strconv"
	"sync"
)

type LockableValue[T SomeValue] struct {
	value SomeValue
	mu    sync.Mutex
}

func (lv *LockableValue[T]) ToString() string {
	lv.mu.Lock()
	defer lv.mu.Unlock()
	return fmt.Sprint("lockable ", lv.value.ToString())
}

func (lv *LockableValue[T]) LockedFromString(v string) {
	lv.mu.Lock()
	defer lv.mu.Unlock()
	lv.value.FromString(v)
}

func (lv *LockableValue[T]) LockedIncrement() {
	lv.mu.Lock()
	defer lv.mu.Unlock()
	lv.value.Increment()
}

type SomeValue interface {
	ToString() string
	FromString(v string) error
	Increment()
}

type IntegerValue struct {
	i int
}

func (iv IntegerValue) ToString() string { return fmt.Sprint(iv.i) }
func (iv *IntegerValue) FromString(v string) error {
	i, err := strconv.Atoi(v)
	if err != nil {
		return err
	}
	iv.i = i
	return nil
}
func (iv *IntegerValue) Increment() { iv.i++ }

func main() {
	var lockable LockableValue[IntegerValue]
	lockable.value = &IntegerValue{}

	lockable.LockedFromString("1234")
	lockable.LockedIncrement()
	fmt.Println("value=", lockable.ToString())
}

Does not compile. ./prog.go:38:29: IntegerValue does not satisfy SomeValue (method FromString has pointer receiver). If we modify the code to make all of the methods have a non-pointer receiver, then FromString() and Increment() methods cannot make changes to the value field on the line that says lockable.value.Increment():

// You can edit this code!
// Click here and start typing.
package main

import (
	"fmt"
	"strconv"
	"sync"
)

type LockableValue[T SomeValue] struct {
	value SomeValue
	mu    sync.Mutex
}

func (lv *LockableValue[T]) ToString() string {
	lv.mu.Lock()
	defer lv.mu.Unlock()
	return fmt.Sprint("lockable ", lv.value.ToString())
}

func (lv *LockableValue[T]) LockedFromString(v string) {
	lv.mu.Lock()
	defer lv.mu.Unlock()
	lv.value.FromString(v)
}

func (lv *LockableValue[T]) LockedIncrement() {
	lv.mu.Lock()
	defer lv.mu.Unlock()
	lv.value.Increment()
}

type SomeValue interface {
	ToString() string
	FromString(v string) error
	Increment()
}

type IntegerValue struct {
	i int
}

func (iv IntegerValue) ToString() string { return fmt.Sprint(iv.i) }
func (iv IntegerValue) FromString(v string) error {
	i, err := strconv.Atoi(v)
	if err != nil {
		return err
	}
	iv.i = i
	return nil
}
func (iv IntegerValue) Increment() { iv.i++ }

func main() {
	var lockable LockableValue[IntegerValue]
	lockable.value = &IntegerValue{}

	lockable.LockedFromString("1234")
	lockable.LockedIncrement()
	fmt.Println("value=", lockable.ToString())
}

compiles, but:

value= 0

Program exited.

Note that to get this to run without a segfault, I had to add this line:

	lockable.value = &IntegerValue{}

So the field is indeed an interface which points to an implementation. If I omit this line, the program crashes with a segfault due to a null pointer exception.

I can understand where embedding the type directly is disallowed, for the reasons given, however if using the type as a named field as "value SomeValue" above is the solution, what is the means to allow the embedded type to be modifiable with methods that have pointer receiver?

I ask this because I indeed have cases where I can abstract functionality over a set of implementations in an orthogonal manner using this technique, except for the pointer-to-receiver issue. I would like the generic type (LockableValue in this case) to be able to call methods on the field (value in this case)

Is this a compiler bug? As we can see above, value is an interface that points to an implementation and not a struct value?

@g-talbot
Copy link

g-talbot commented Oct 8, 2023

Ah. LockableValue[*IntegerValue]. Sorry it did look like a compiler bug at first. Thanks. Sorry for complaining.

AZ-X added a commit to AZ-X/pique that referenced this issue Jan 15, 2024
repique does talk to the 'build system' and enjoyed the early relaxation

FrozenDueToAge/release-blocker:
golang/go#50731
golang/go#49030
golang/go#48334
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants