Filing this for completeness sake, since it was mentioned in https://github.com/golang/go/issues/21670 that this proposal had been discussed privately prior to Go 1. Just like literals allow the construction of slice, struct, and map values, I propose "interface literals" which specifically construct values that satisfy an interface. The syntax would mirror that of struct literals, where field names would correspond to method names. The original idea is proposed by @Sajmani in https://github.com/golang/go/issues/21670#issuecomment-325739411.
Conceptually, this proposal would transform the following initializer
f := func(p int) { /* implementation */ }
x := interface{
Name(p int)
}{f}
to the following type declaration and struct initializer
type _impl_Name_int struct {
f func(p int)
}
func(v _impl_Name_int) Name(p int) { v.f(p) }
// … in some other scope
f := func(p int) { /* implementation */ }
var x interface{
Name(p int)
} = _impl_Name_int{f}
As an extension to what’s mentioned in #21670, I propose that fields be addressable by both method names and field names, like in this example:
type ReadWriteSeekCloser interface {
ReadWriteSeeker
Closer
}
f := os.Open("file")
calls := 0
return ReadWriteSeekCloser{
ReadWriteSeeker: f,
Close: func() error {
if calls < 1 {
return f.Close()
}
return nil
},
}
The default value for a method is nil. Calling a nil method will cause a panic. As a corollary, the interface can be made smaller to be any subset of the original declaration. The value can no longer be converted back to satisfy the original interface. See the following modified example (from @neild in https://github.com/golang/go/issues/21670#issuecomment-329251670):
type io interface {
Read(p []byte) (n int, err error)
ReadAt(p []byte, off int64) (n int, err error)
WriteTo(w io.Writer) (n int64, err error)
}
// 3 method -> 2^3 = 8 subsets
func fn() io.Reader {
return io{
Read: strings.NewReader(“”),
}
}
The nil values for ReadAt and WriteTo make it so the “downcasted” io.Reader can no longer be recast to an io. This provides a clean way to promote known methods, with the side effect that the struct transformation described above won't be a valid implementation of this proposal, since casting does not work this way when calling a nil function pointer through a struct.
Although this proposal brings parity between struct and interface initialization and provides easy promotion of known methods, I don’t think this feature would dramatically improve the way Go programs are written.
We may see more usage of closures like in this sorting example (now obviated because of sort.Slice):
arr := []int{1,2,3,4,5}
sort.Sort(sort.Interface{
Len: func() int { return len(arr) },
Swap: func(i, j int) {
temp := arr[i]
arr[i] = arr[j]
arr[j] = temp
},
Less: func(i, j int) bool { return arr[i] < arr[j] },
})
Promotion of known methods also avoids a lot of boilerplate, although I’m not sure that it is a common enough use case to warrant a language feature.
For instance, if I wanted to wrap an io.Reader, but also let through implementations of io.ReaderAt, io.WriterTo, and io.Seeker, I would need seven different wrapper types, each of which embeds these types:
type wrapper1 struct {
io.Reader
io.ReaderAt
}
type wrapper2 struct {
io.Reader
io.WriterTo
}
type wrapper3 struct {
io.Reader
io.Seeker
}
type wrapper4 struct {
io.Reader
io.ReaderAt
io.WriterTo
}
type wrapper5 struct {
io.Reader
io.ReaderAt
io.Seeker
}
type wrapper6 struct {
io.Reader
io.WriterTo
io.Seeker
}
type wrapper7 struct {
io.Reader
io.ReaderAt
io.WriterTo
io.Seeker
}
Here is the relevant change to the grammar (under the composite literal section of the spec):
LiteralType = StructType | InterfaceType | ArrayType | "[" "..." "]" ElementType |
SliceType | MapType | TypeName .
How would this work in terms of reflection? What would be the result of calling reflect.ValueOf on one of these interface literals? This would create the case that isn't currently possible where an interface isn't actually "wrapping" some underlying value. There is also the question of how they would work with a switch t.(type) statement.
I assume you mean to ask that if
i is an interface literal as described above
iv := reflect.ValueOf(i)
u := iv.Interface()
uv := reflect.ValueOf(u)
What would be the kind of uv? Also what operations on uv are valid?
You are right in that there isn't an underlying value being wrapped. That being said, since type switches can already switch on interfaces, doing so on an interface literal would simply not satisfy cases that check for concrete types.
b := []byte("some randomly accessible string")
pos := 0
rs := io.ReadSeeker{
Read: func(p []byte) (n int, err error) {
/* implementation */
}
Seek: func(offset int64, whence int) (int64, error) {
/* implementation */
}
}
r := io.Reader(rs)
switch r.(type) {
case *bytes.Buffer:
// does not satisfy
case io.ReadWriteCloser:
// does not satisfy
case io.ReadSeeker:
// does satisfy
}
As to the representation of a literal's reflected value, if the same reflect package is used for Go 2, the underlying value can be a MethodSet. This does not have to correspond to its runtime representation, but this is a simple abstraction for the reflect package.
A MethodSet is just an interface that references all methods in the underlying value. Operations on a MethodSet are nearly identical to operations on an Interface. From the above example, if uv.Kind() is a MethodSet, then uv.Interface() is no longer a valid operation.
ut := uv.Type() will return a type with all of the underlying methods. Similar to an interface type, ut.Method and ut.MethodByName will return Methods whose signatures do not have a receiver and whose Func fields are nil.
I think the primary problem with this proposal is that it allows nil methods, which panic when invoked.
On the other hand, nil interfaces are extremely common, even though one could argue that they are also a violation of the contract described above, since users expect to be able to invoke its methods.
Additionally, I think allowing nil methods to prevent a downcasted interface from type-asserting into the original interface sounds nice since it allows the promotion of known methods. However, this behavior only exists because nil methods are allowed, and allows the runtime to convert an "unsafe" (non-invocable) interface to a "safe" (invocable) interface. This behavior implies that non-invocable interfaces shouldn't exist in the first place, and is too subtle and surprising.
The only alternative I can think of is to make a nil method provide some default no-op implementation of a method. Although this is safer than the previously mentioned, it seems just as subtle and surprising, but less powerful.
Ultimately, it appears impossible to require that no field in an interface literal is nil, since a value that is assigned to it could be nil at run-time. The only way to guarantee no field is nil would be to restrict each field to be a function literal or top-level function. However, this pretty much loses all of the power of interface literals, since it is only a marginal improvement (LoC-wise) over a type which is defined to implement an interface.
Can this be added to #33892 for review?
/cc @golang/proposal-review
This was reviewed and marked as NeedsInvestigation. At some point we'll re-review these issues.
Most helpful comment
How would this work in terms of reflection? What would be the result of calling
reflect.ValueOfon one of these interface literals? This would create the case that isn't currently possible where an interface isn't actually "wrapping" some underlying value. There is also the question of how they would work with aswitch t.(type)statement.