This initial proposal is to remove all uses of the empty interface (interface{}) from the standard library for Go 2.
@chowey listed these in the generics proposal discussion (https://github.com/golang/go/issues/15292):
Finally, the standard library is riddled with interface{} alternatives where generics would work more safely:
• heap.Interface (https://golang.org/pkg/container/heap/#Interface)
• list.Element (https://golang.org/pkg/container/list/#Element)
• ring.Ring (https://golang.org/pkg/container/ring/#Ring)
• sync.Pool (https://golang.org/pkg/sync/#Pool)
• upcoming sync.Map (https://tip.golang.org/pkg/sync/#Map)
• atomic.Value (https://golang.org/pkg/sync/atomic/#Value)There may be others I'm missing. The point being, each of the above are where I would expect generics to be useful.
The type interface{} represents an item that has no required ability. The above cases use it to store items of any type in a container, but an item with no required ability may not be storable. encoding/json uses it with the assumption that the item has a specific kind of structure which in my opinion breaks the annotation of “no required ability”.
Compile-time type safety is a key feature of Go and the standard library should have checks in every case. Using reflect (run-time type safety) or wrappers (compile-time type safety) incur performance or readability costs. In both cases a type assertion is required. Code generation is probably excessively complex for standard use and Go already explicitly does not have regular C-like preprocessor directives.
This is the key reason to implement generics, or there may be alternative Go 1 implementations that improve over the use of interface{}, or these necessary types could be added to the built-in type list at considerable expense.
func panic(v interface{})
func recover() interface{}
container/heap
func Pop(h Interface) interface{}
func Push(h Interface, x interface{})
func Remove(h Interface, i int) interface{}
container/list
func (I *List) InsertAfter(v interface{}, mark *Element) *Element
func (I *List) InsertBefore(v interface{}, mark *Element) *Element
func (I *List) PushBack(v interface{}) *Element
func (I *List) PushFront(v interface{}) *Element
func (I *List) Remove(e *Element) interface{}
container/ring
func (r *Ring) Do(f func(interface{}))
context
func WithValue(parent Context, key, val interface{}) Context
crypto/x509
func CreateCertificate(rand io.Reader, template, parent *Certificate, pub, priv interface{}) (cert []byte, err error)
func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error)
func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)
func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error)
func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error)
func (c *Certificate) CreateCRL(rand io.Reader, priv interface{}, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error)
database/sql
func (c *Conn) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (c *Conn) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (c *Conn) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryRow(query string, args ...interface{}) *Row
func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row
func Named(name string, value interface{}) NamedArg
func (n *NullBool) Scan(value interface{}) error
func (n *NullFloat64) Scan(value interface{}) error
func (n *NullInt64) Scan(value interface{}) error
func (ns *NullString) Scan(value interface{}) error
func (r *Row) Scan(dest ...interface{}) error
func (rs *Rows) Scan(dest ...interface{}) error
func (s *Stmt) Exec(args ...interface{}) (Result, error)
func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (Result, error)
func (s *Stmt) Query(args ...interface{}) (*Rows, error)
func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error)
func (s *Stmt) QueryRow(args ...interface{}) *Row
func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *Row
func (tx *Tx) Exec(query string, args ...interface{}) (Result, error)
func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryRow(query string, args ...interface{}) *Row
func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row
database/sql/driver
func IsScanValue(v interface{}) bool
func IsValue(v interface{}) bool
func (n NotNull) ConvertValue(v interface{}) (Value, error)
func (n Null) ConvertValue(v interface{}) (Value, error)
encoding/asn1
func Marshal(val interface{}) ([]byte, error)
func Unmarshal(b []byte, val interface{}) (rest []byte, err error)
func UnmarshalWithParams(b []byte, val interface{}, params string) (rest []byte, err error)
encoding/binary
func Read(r io.Reader, order ByteOrder, data interface{}) error
func Size(v interface{}) int
func Write(w io.Writer, order ByteOrder, data interface{}) error
encoding/gob
func Register(value interface{})
func RegisterName(name string, value interface{})
func (dec *Decoder) Decode(e interface{}) error
func (enc *Encoder) Encode(e interface{}) error
encoding/json
func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
func (dec *Decoder) Decode(v interface{}) error
func (enc *Encoder) Encode(v interface{}) error
encoding/xml
func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
func (d *Decoder) Decode(v interface{}) error
func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error
func (enc *Encoder) Encode(v interface{}) error
func (enc *Encoder) EncodeElement(v interface{}, start StartElement) error
expvar
func (f Func) Value() interface{}
fmt
func Errorf(format string, a ...interface{}) error
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
func Scan(a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Scanln(a ...interface{}) (n int, err error)
func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string
func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
go/ast
func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) error
func Print(fset *token.FileSet, x interface{}) error
go/format
func Node(dst io.Writer, fset *token.FileSet, node interface{}) error
go/parser
func ParseExprFrom(fset *token.FileSet, filename string, src interface{}, mode Mode) (ast.Expr, error)
func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error)
go/printer
func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error
func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error
html/template
func HTMLEscaper(args ...interface{}) string
func IsTrue(val interface{}) (truth, ok bool)
func JSEscaper(args ...interface{}) string
func URLQueryEscaper(args ...interface{}) string
func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
log
func Fatal(v ...interface{})
func Fatalf(format string, v ...interface{})
func Fatalln(v ...interface{})
func Panic(v ...interface{})
func Panicf(format string, v ...interface{})
func Panicln(v ...interface{})
func Print(v ...interface{})
func Printf(format string, v ...interface{})
func Println(v ...interface{})
func (l *Logger) Fatal(v ...interface{})
func (l *Logger) Fatalf(format string, v ...interface{})
func (l *Logger) Fatalln(v ...interface{})
func (l *Logger) Panic(v ...interface{})
func (l *Logger) Panicf(format string, v ...interface{})
func (l *Logger) Panicln(v ...interface{})
func (l *Logger) Print(v ...interface{})
func (l *Logger) Printf(format string, v ...interface{})
func (l *Logger) Println(v ...interface{})
net/rpc
func Register(rcvr interface{}) error
func RegisterName(name string, rcvr interface{}) error
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call
func (server *Server) Register(rcvr interface{}) error
func (server *Server) RegisterName(name string, rcvr interface{}) error
net/textproto
func (c *Conn) Cmd(format string, args ...interface{}) (id uint, err error)
func (w *Writer) PrintfLine(format string, args ...interface{}) error
reflect
func DeepEqual(x, y interface{}) bool
func Swapper(slice interface{}) func(i, j int)
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
func (v Value) Interface() (i interface{})
runtime
func KeepAlive(interface{})
func SetFinalizer(obj interface{}, finalizer interface{})
runtime/pprof
func (p *Profile) Add(value interface{}, skip int)
func (p *Profile) Remove(value interface{})
sort
func Slice(slice interface{}, less func(i, j int) bool)
func SliceIsSorted(slice interface{}, less func(i, j int) bool) bool
func SliceStable(slice interface{}, less func(i, j int) bool)
sync
func (m *Map) Delete(key interface{})
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
func (m *Map) Range(f func(key, value interface{}) bool)
func (m *Map) Store(key, value interface{})
func (p *Pool) Get() interface{}
func (p *Pool) Put(x interface{})
sync/atomic
func (v *Value) Load() (x interface{})
func (v *Value) Store(x interface{})
testing
func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Log(args ...interface{})
func (c *B) Logf(format string, args ...interface{})
func (c *B) Skip(args ...interface{})
func (c *B) Skipf(format string, args ...interface{})
func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Skip(args ...interface{})
func (c *T) Skipf(format string, args ...interface{})
testing/quick
func Check(f interface{}, config *Config) error
func CheckEqual(f, g interface{}, config *Config) error
text/template
func HTMLEscaper(args ...interface{}) string
func IsTrue(val interface{}) (truth, ok bool)
func JSEscaper(args ...interface{}) string
func URLQueryEscaper(args ...interface{}) string
func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
text/template/parse
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (map[string]*Tree, error)
func New(name string, funcs ...map[string]interface{}) *Tree
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error)
From a quick pass through the experience reports (https://github.com/golang/go/wiki/ExperienceReports):
https://blog.samwhited.com/2017/08/the-case-for-interface/
https://github.com/blitzprog/go-channel-type-casting
https://gist.github.com/kevinburke/a10aed6d8d07ecd5efe658b21cd168c1
https://deedlefake.com/2017/07/the-problem-with-interfaces/
Here’s an overview of the use of interface{} in each standard library case. Some of these implementations are complex and my interpretation may be wrong or there may not be enough detail here.
builtin.panic
: https://github.com/golang/go/blob/release-branch.go1.9/src/runtime/panic.go#L414
Constructs a panic string, expects types that can be converted to string.
builtin.recover
: https://github.com/golang/go/blob/release-branch.go1.9/src/runtime/panic.go#L560
Returns the item passed into panic.
container/heap.Pop, Push, Remove
: https://github.com/golang/go/blob/release-branch.go1.9/src/container/heap/heap.go
A container/heap client constructs a type that adheres to heap.Interface and these heap package functions call heap.Interface methods. The provided type may define a non-interface{} type contained but will have to do a type assertion on calling heap.Pop if so.
container/list.InsertAfter, InsertBefore, PushBack, PushFront, Remove
: https://github.com/golang/go/blob/release-branch.go1.9/src/container/list/list.go
sync.*Map
: https://github.com/golang/go/blob/release-branch.go1.9/src/sync/map.go
sync.*Pool
: https://github.com/golang/go/blob/release-branch.go1.9/src/sync/pool.go
sync/atomic.*Value
: https://github.com/golang/go/blob/release-branch.go1.9/src/sync/atomic/value.go
Unlike container/heap, container/list provides the type constructed of list.Element structs. Each Element has a “Value interface{}” field where the reference to the client’s item is stored. Remove requires a type assertion for non-interface{} types referenced by Value.
container/ring.Do
: https://github.com/golang/go/blob/release-branch.go1.9/src/container/ring/ring.go#L134
container/ring follows the same pattern as container/list by providing a container type where each Ring (item) in the complete Ring has a “Value interface{}” field. Do will execute the provided function on each Ring in the Ring, in which a type assertion is required for non-interface{} item types. Clients access elements by directly referencing Value in the struct which also requires a type assertion.
context.WithValue
: https://github.com/golang/go/blob/release-branch.go1.9/src/context/context.go#L467
The provided parent context has a data store accessible by the interface method “Value(key interface{}) interface{}”. This function copies the parent context and sets the key to val. “Packages that define a Context key should provide type-safe accessors for the values stored using that key”
crypto/x509.CreateCertificate, CreateCertificateRequest, MarshalPKIXPublicKey, ParsePKCS8PrivateKey, ParsePKIXPublicKey, *Certificate.CreateCRL
: https://github.com/golang/go/blob/release-branch.go1.9/src/crypto/x509/x509.go
“The parameter pub is the public key of the signee and priv is the private key of the signer…All keys types that are implemented via crypto.Signer are supported (This includes *rsa.PublicKey and *ecdsa.PublicKey.)”
crypto defines a Signer interface that works with named types PublicKey, PrivateKey represented by interface{}, I’m not sure why those crypto types aren’t already being used in crypto/x509.
database/sql: Exec, Query
: https://github.com/golang/go/blob/release-branch.go1.9/src/database/sql/driver/driver.go#L96
The provided arguments are passed to the driver as a processed slice of interface{}.
database/sql: Scan
: https://github.com/golang/go/blob/release-branch.go1.9/src/database/sql/convert.go#L220
Values returned from the driver are converted from string, []byte, time.Time, or nil into similar outputs or through a reflect-driven conversion to friendly numeric types.
database/sql/driver
: https://github.com/golang/go/blob/release-branch.go1.9/src/database/sql/driver/types.go
The database driver provides ValueConverter types or uses database/sql/driver ValueConverters that convert Go types (passed in as interface{}) into Value (an interface{}) that represents a Value the database can handle (nil, int64, float64, bool, []byte, string, or time.Time).
encoding/asn1.Marshal
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/asn1/marshal.go#L652
encoding/json.Marshal
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/json/encode.go#L159
encoding/xml.Marshal
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/xml/marshal.go#L72
Uses reflect with struct tags to compile a []byte from an input interface{} that adheres to possible encodable structures (or an error is returned).
encoding/asn1.Unmarshal
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/asn1/asn1.go#L1011
encoding/json.Unmarshal
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/json/decode.go#L96
encoding/xml.Unmarshal
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/xml/read.go#L126
The struct provided by interface{} that’s filled in by the function must use upper case fields.
encoding/binary.Read
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/binary/binary.go#L161
The output data interface{} can be a pointer to any bool or integer type or a slice of any bool or integer types.
encoding/binary.Size
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/binary/binary.go#L374
Returns the size of the input interface{} for slices, arrays, structs, or any numeric type, otherwise it returns -1.
encoding/binary.Write
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/binary/binary.go#L260
Write the binary encoding of the input into the provided io.Writer. First a type switch is tried for integer typed slice, integer typed pointer, or direct integer types, otherwise a reflect-based encoding is tried.
encoding/gob.Register, RegisterName
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/gob/type.go#L836
Registers the base type of the input during initialization. RegisterName uses a provided string for the type name.
encoding/gob.*Decoder.Decode
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/gob/decoder.go#L178
encoding/json.*Decoder.Decode
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/json/stream.go#L43
encoding/xml.*Decoder.Decode
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/xml/read.go#L132
“Decode reads the next value from the input stream and stores it in the data represented by the empty interface value. If e is nil, the value will be discarded. Otherwise, the value underlying e must be a pointer to the correct type for the next data item received.”
encoding/gob.*Encoder.Encode
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/gob/encoder.go#L174
encoding/json.*Encoder.Encode
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/json/stream.go#L188
encoding/xml.*Encoder.Encode
: https://github.com/golang/go/blob/release-branch.go1.9/src/encoding/xml/marshal.go#L154
“Encode transmits the data item represented by the empty interface value, guaranteeing that all necessary type information has been transmitted first. Passing a nil pointer to Encoder will panic, as they cannot be transmitted by gob.”
expvar.Func.Value
: https://github.com/golang/go/blob/release-branch.go1.9/src/expvar/expvar.go#L240
“Func implements Var by calling the function and formatting the returned value using JSON.”
fmt
: https://github.com/golang/go/blob/release-branch.go1.9/src/fmt/print.go#L604
log
: https://github.com/golang/go/blob/release-branch.go1.9/src/log/log.go#L178
net/textproto
: https://github.com/golang/go/blob/release-branch.go1.9/src/net/textproto/textproto.go#L114
testing
: https://github.com/golang/go/blob/release-branch.go1.9/src/testing/testing.go
A type switch is used before reflection to parse depending on the underlying type of each input interface{} associated with the format verb (%v, %d).
go/ast.Print
: https://github.com/golang/go/blob/release-branch.go1.9/src/go/ast/print.go#L43
Writes the type of the provided item.
go/format.Node
: https://github.com/golang/go/blob/release-branch.go1.9/src/go/format/format.go#L33
“Node formats node in canonical gofmt style and writes the result to dst. The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt. Node does not modify node. Imports are not sorted for nodes representing partial source files (i.e., if the node is not an *ast.File or a *printer.CommentedNode not wrapping an *ast.File).”
go/parser.ParseExprFrom
: https://github.com/golang/go/blob/release-branch.go1.9/src/go/parser/interface.go#L180
“ParseExprFrom is a convenience function for parsing an expression. The arguments have the same meaning as for ParseFile, but the source must be a valid Go (type or value) expression. Specifically, fset must not be nil.”
go/parser.ParseFile
: https://github.com/golang/go/blob/release-branch.go1.9/src/go/parser/interface.go#L84
“If src != nil, ParseFile parses the source from src and the filename is only used when recording position information. The type of the argument for the src parameter must be string, []byte, or io.Reader. If src == nil, ParseFile parses the file specified by filename.”
go/printer.Fprint
: https://github.com/golang/go/blob/release-branch.go1.9/src/go/printer/printer.go#L1345
“Fprint "pretty-prints" an AST node to output for a given configuration cfg. Position information is interpreted relative to the file set fset. The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.”
html/template.HTMLEscaper
: https://github.com/golang/go/blob/release-branch.go1.9/src/html/template/escape.go#L857
text/template.HTMLEscaper
: https://github.com/golang/go/blob/release-branch.go1.9/src/text/template/funcs.go#L636
html/template.JSEscaper
: https://github.com/golang/go/blob/release-branch.go1.9/src/html/template/escape.go#L873
text/template.JSEscaper
: https://github.com/golang/go/blob/release-branch.go1.9/src/text/template/funcs.go#L621
html/template.URLQueryEscaper
: https://github.com/golang/go/blob/release-branch.go1.9/src/html/template/escape.go#L879
text/template.URLQueryEscaper
: https://github.com/golang/go/blob/release-branch.go1.9/src/text/template/funcs.go#L627```
“evalArgs formats the list of arguments into a string. It is therefore equivalent to fmt.Sprint(args...) except that each argument is indirected (if a pointer), as required, using the same rules as the default string evaluation during template execution.”
html/template.IsTrue
: https://github.com/golang/go/blob/release-branch.go1.9/src/html/template/template.go#L479
text/template.IsTrue
: https://github.com/golang/go/blob/release-branch.go1.9/src/text/template/exec.go#L282
“IsTrue reports whether the value is 'true', in the sense of not the zero of its type, and whether the value has a meaningful truth value. This is the definition of truth used by if and other such actions.”
html/template.*Template.Execute
: https://github.com/golang/go/blob/release-branch.go1.9/src/html/template/template.go#L118
text/template.*Template.Execute
: https://github.com/golang/go/blob/release-branch.go1.9/src/text/template/exec.go#L183
The template client provides data that map to template actions (https://github.com/golang/go/blob/release-branch.go1.9/src/text/template/parse/node.go).
net/rpc.Register
: https://github.com/golang/go/blob/release-branch.go1.9/src/net/rpc/server.go#L231
“Register publishes in the server the set of methods of the receiver value that satisfy the following conditions:
- exported method of exported type
- two arguments, both of exported type
- the second argument is a pointer
- one return value, of type error
It returns an error if the receiver is not an exported type or has no suitable methods. It also logs the error using package log. The client accesses each method using a string of the form "Type.Method", where Type is the receiver's concrete type.”
net/rpc.*Client.Call, Go
: https://github.com/golang/go/blob/release-branch.go1.9/src/net/rpc/client.go#L315
A net/rpc.ClientCodec is responsible for converting the rpc interface{} argument set into a representation that is understandable by the server being called.
reflect.DeepEqual
: https://github.com/golang/go/blob/release-branch.go1.9/src/reflect/deepequal.go#L187
Deep Equality is defined for arrays, structs, func, interface, map, pointer, slice, numbers, bools, strings, and channels.
reflect.Swapper
: https://github.com/golang/go/blob/release-branch.go1.9/src/reflect/swapper.go#L13
“Swapper returns a function that swaps the elements in the provided slice. Swapper panics if the provided interface is not a slice.”
reflect.TypeOf
: https://github.com/golang/go/blob/release-branch.go1.9/src/reflect/type.go#L1398
“TypeOf returns the reflection Type that represents the dynamic type of i. If i is a nil interface value, TypeOf returns nil.”
reflect.ValueOf
: https://github.com/golang/go/blob/release-branch.go1.9/src/reflect/value.go#L2113
“ValueOf returns a new Value initialized to the concrete value stored in the interface i. ValueOf(nil) returns the zero Value.”
reflect.Value.Interface
: https://github.com/golang/go/blob/release-branch.go1.9/src/reflect/value.go#L930
“Interface returns v's current value as an interface{}. It is equivalent to: var i interface{} = (v's underlying value). It panics if the Value was obtained by accessing unexported struct fields.”
runtime.KeepAlive
: https://github.com/golang/go/blob/release-branch.go1.9/src/runtime/mfinal.go#L490
“KeepAlive marks its argument as currently reachable. This ensures that the object is not freed, and its finalizer is not run, before the point in the program where KeepAlive is called.”
runtime.SetFinalizer
: https://github.com/golang/go/blob/release-branch.go1.9/src/runtime/mfinal.go#L309
“The argument obj must be a pointer to an object allocated by calling new, by taking the address of a composite literal, or by taking the address of a local variable. The argument finalizer must be a function that takes a single argument to which obj's type can be assigned, and can have arbitrary ignored return values. If either of these is not true, SetFinalizer may abort the program.”
runtime/pprof.*Profile.Add, Remove
: https://github.com/golang/go/blob/release-branch.go1.9/src/runtime/pprof/pprof.go#L260
“Add adds the current execution stack to the profile, associated with value. Add stores value in an internal map, so value must be suitable for use as a map key and will not be garbage collected until the corresponding call to Remove. Add panics if the profile already contains a stack for value.”
sort.Slice, SliceIsSorted, SliceStable
: https://github.com/golang/go/blob/release-branch.go1.9/src/sort/sort.go#L247
“Slice sorts the provided slice given the provided less function…The function panics if the provided interface is not a slice.”
testing/quick.Check, CheckEqual
: https://github.com/golang/go/blob/release-branch.go1.9/src/testing/quick/quick.go#L262
“Check looks for an input to f, any function that returns bool, such that f returns false. It calls f repeatedly, with arbitrary values for each argument. If f returns false on a given input, Check returns that input as a *CheckError.”
text/template/parse.Parse, New, *Tree.Parse
: https://github.com/golang/go/blob/release-branch.go1.9/src/text/template/parse/parse.go#L51
“Parse returns a map from template name to parse.Tree, created by parsing the templates described in the argument string. The top-level template will be given the specified name. If an error is encountered, parsing stops and an empty map is returned with the error.”
This is interesting to look at. I took a shot at classifying these:
Prototypical example: fmt.Print
prints any value. I don't know what you would replace interface{}
with here.
Prototypical example: context.Value
stores an arbitrary key/value pair. This differs slightly from the previous case in that the value may be retrieved at a later time, and must be type-asserted into the proper concrete type by the user. I think interface{}
is reasonable in this case as well.
Prototypical example: encoding/json
. These are reflection-based functions which don't operate on arbitrary data, but accept such a variety of types that defining a more limited type signature seems challenging. A non-encoding example is rpc/server.Register
.
Prototypical example: container/*
. These are types which store values of a consistent type, possibly satisfying a specific interface. This is an obvious case for improvement with some form of type parameterization.
Prototypical example: sort.Sort
. This is an obvious case for improvement with some form of function parameterization.
Prototypical example: database/sql.Value
is an interface{}
defined to hold one of six possible types. Some form of sum type or union might be useful in these cases.
In every case interface{} is used to describe a type that cannot be fully described programmatically with Go.
I propose the solution to put each interface{} behind a type (type Formatable interface{}
) and document the constraints in comments, for every case of interface{} in the standard library API.
What are the constraints that would be documented? fmt.Print
can operate on any value.
@neild
func Print(a ...interface{}) (n int, err error)
Print formats using the default formats for its operands and writes to standard output. Spaces are added between operands when neither is a string. It returns the number of bytes written and any write error encountered.
The constraint would be that it can be formatted for %v. The type documentation would be a place to describe fmt verbs which are the constraint for the Printf functions.
Printf documentation would hold more value than the Print's "anything works". Ideally the compiler would verify each input type with the corresponding format string verb, but since this is not possible it would be described as part of the input type documentation.
For fmt and probably most cases this would just be moving documentation around, but the added value is the ability to guess right away when looking at a function/method signature what the type constraints are beyond "you can try anything".
@neild database/sql.Value
can hold more then those specific six types. It is effectively an "any" as well, dependent on what the database driver can read and convert.
@kardianos Apologies, I meant to write database/sql/driver.Value
:
https://godoc.org/database/sql/driver#Value
@neild Same thing still applies, but the docs are outdated now. Shoot. Gotta send a CL.
Change https://golang.org/cl/84636 mentions this issue: database/sql/driver: update Value doc, can be driver supported type
In addition to @neild's classification above, I would like to share my learnings about the empty interface in the sort
package:
Usage:
sort.Slice
, sort.SliceStable
and sort.SliceIsSorted
).Purpose
interface{}
here is to represent an input type which cannot be described programmatically in Go at the moment. i.e. accepting a slice of values of any type.Moreover, the calls to these functions panic
if we do not pass a slice as the first argument.
So replacing the empty interface from the signatures might be a welcome change for preventing runtime failures.
NOTE: This explanation is to the best of my understanding and there might be deeper implications which I might be overlooking at the moment because of my limited experience in writing Go code.
Thanks for all the investigation. This is going to be entirely dependent on generics. And, of course, there are cases where do need to keep the empty interface, as in fmt.Print
. Closing this issue in favor of the general generics issue, #15292.
Most helpful comment
This is interesting to look at. I took a shot at classifying these:
Functions that accept ~anything.
Prototypical example:
fmt.Print
prints any value. I don't know what you would replaceinterface{}
with here.Functions that store ~anything, and return it later.
Prototypical example:
context.Value
stores an arbitrary key/value pair. This differs slightly from the previous case in that the value may be retrieved at a later time, and must be type-asserted into the proper concrete type by the user. I thinkinterface{}
is reasonable in this case as well.Functions operating on a broad spectrum of types.
Prototypical example:
encoding/json
. These are reflection-based functions which don't operate on arbitrary data, but accept such a variety of types that defining a more limited type signature seems challenging. A non-encoding example isrpc/server.Register
.Containers.
Prototypical example:
container/*
. These are types which store values of a consistent type, possibly satisfying a specific interface. This is an obvious case for improvement with some form of type parameterization.Algorithms.
Prototypical example:
sort.Sort
. This is an obvious case for improvement with some form of function parameterization.Functions operating on a specific set of types.
Prototypical example:
database/sql.Value
is aninterface{}
defined to hold one of six possible types. Some form of sum type or union might be useful in these cases.