You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Go compiler doesn't allow pointer-to-receiver methods on an interface struct field value when that interface type is defined with a generic parameter.
#63447
Closed
g-talbot opened this issue
Oct 8, 2023
· 2 comments
Discovered an issue where, when a generic interface is specified as a struct type parameter, a value of that interface type in the struct, though it requires a pointer-to-concrete-implementation, will not allow the concrete implementations to have pointer-to-receiver typed methods. Discussed this in this comment, but figured it was worth a bug report.
I came across this when having a similar problem, but the suggestion given in that issue 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)
I believe this to be an actual compiler bug, as the value field of LockableValue above is indeed a pointer-to-interface (the lockable.value = &IntegerValue{} is required for the program to not crash), but the compiler won't allow e.g. IntegerValue to have pointer-to-receiver methods.
What did you expect to see?
I expect the first example, where IntegerValue has pointer-to-receiver methods, to compile, and for this program to output value= 1235
What did you see instead?
Did not compile, and when I changed the offending methods to not take pointer-to-receiver, the program output value= 0.
The text was updated successfully, but these errors were encountered:
g-talbot
changed the title
affected/package:
Go compiler doesn't allow pointer-to-receiver methods on an interface struct field value when that interface type is defined with a generic parameter.
Oct 8, 2023
working as expected, you need a pointer to the type
Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.
working as expected, you need a pointer to the type
Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?Go playground here: https://go.dev/play/p/5p6Y3gEiMZe
What did you do?
Discovered an issue where, when a generic interface is specified as a struct type parameter, a value of that interface type in the struct, though it requires a pointer-to-concrete-implementation, will not allow the concrete implementations to have pointer-to-receiver typed methods. Discussed this in this comment, but figured it was worth a bug report.
I came across this when having a similar problem, but the suggestion given in that issue 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):
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()
andIncrement()
methods cannot make changes to thevalue
field on the line that sayslockable.value.Increment()
:compiles, but:
Note that to get this to run without a segfault, I had to add this line:
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)I believe this to be an actual compiler bug, as the
value
field ofLockableValue
above is indeed a pointer-to-interface (thelockable.value = &IntegerValue{}
is required for the program to not crash), but the compiler won't allow e.g.IntegerValue
to have pointer-to-receiver methods.What did you expect to see?
I expect the first example, where
IntegerValue
has pointer-to-receiver methods, to compile, and for this program to outputvalue= 1235
What did you see instead?
Did not compile, and when I changed the offending methods to not take pointer-to-receiver, the program output
value= 0
.The text was updated successfully, but these errors were encountered: