-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
(See some similarities with #26056)
What version of Go are you using (go version
)?
N/A
Does this issue reproduce with the latest release?
N/A
What operating system and processor architecture are you using (go env
)?
N/A
What did you do?
Wanted to write code using slices of possibly-unknown types, efficiently.
What did you expect to see?
A way to get some of the utility of interfaces, without the massive allocator overhead.
What did you see instead?
If you have a slice of things which satisfy an interface, and you want a slice of that interface, you have to manually rebox the items. A slice of 1M interfaces contains 1M pointers. This is horrible.
What I don't have: any idea how to produce a workable/clean syntax.
What I do have: A description of a thing that I think would be very useful for developers confronted with this.
The basic concept: An interface-slice type, which is distinct from a slice-of-interfaces. An interface-slice is assignable from a slice of objects which satisfy its interface. There is implicit boxing/unboxing involved, but instead of boxing/unboxing each item to create the slice, boxing/unboxing happens only at the time when you're trying to access an item.
So, for instance:
type Strint int
func (s Strint) String() string {
return fmt.Sprintf("~%d~", s)
}
var SliceOfStringer Stringer[] // this syntax is not valid, bear with me
var strints []Strint = []Strint{1, 2, 3}
SliceOfStringer = strints // note the lack of boxing!
for _, v := range SliceOfStringer {
// here, v is a Stringer interface; on each iteration of the loop, it holds one
// of the values from strints
fmt.Printf("%v\n", v)
}
if slice, ok := SliceOfStringer.([]Strint); ok {
// here, slice will contain the original slice, and no further boxing/unboxing is needed
}
Why do I think this would be useful? Because code that deals with large numbers of objects, which might be of multiple types, is currently allocating millions of interfaces. In many cases, it would be adequately served by a handful of interface-slices wrapping slices of the underlying concrete objects. If the interfaces were being used only for brief inner loops, many of them might never escape to the heap, and thus, not require allocation at all.
The best we can do right now is to use interface{}
, and type assertions. You can do okay with this, but you lose all the documentary power of being able to specify that, rather than any arbitrary object, you are looking only for (1) slices, of (2) things that satisfy a particular interface. You also can't handle a new arbitrary item which satisfies your interface. For instance, consider what would happen if you tried to do this for Stringer; how do you check your interface{} against every type that implements Stringer without complex and expensive reflect
checks? If you could instead get a slice known to be a slice of things implementing Stringer, and could iterate through it as if it were a slice of Stringer interfaces, life would be better.
I don't have a good handle of a syntax for this. interface[]{}
almost works, but it can't be used for a named interface type. If this were just added as a new rule for []Interface (permitting assignments from compatible-ish slices), it would probably impose costs on any and all code using []Interface already, and possibly introduce very strange bugs. So I'm completely stumped on syntax.
This doesn't seem to be quite the same case as generics; the goal isn't to generate templated code for each specific instance of a slice satisfying the interface, but to have runtime interface-handling code which better handles the fairly common case of slices of a concrete type.