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

proposal: Go 2: remove interface{} use from the standard library #23077

Closed
pciet opened this issue Dec 10, 2017 · 13 comments
Closed

proposal: Go 2: remove interface{} use from the standard library #23077

pciet opened this issue Dec 10, 2017 · 13 comments
Labels
FrozenDueToAge Proposal v2 A language change or incompatible library change
Milestone

Comments

@pciet
Copy link
Contributor

pciet commented Dec 10, 2017

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 (#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.

@gopherbot gopherbot added this to the Proposal milestone Dec 10, 2017
@bradfitz bradfitz added the v2 A language change or incompatible library change label Dec 10, 2017
@pciet
Copy link
Contributor Author

pciet commented Dec 12, 2017

builtin

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)

@pciet
Copy link
Contributor Author

pciet commented Dec 13, 2017

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.”

@neild
Copy link
Contributor

neild commented Dec 13, 2017

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 replace interface{} 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 think interface{} 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 is rpc/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 an interface{} defined to hold one of six possible types. Some form of sum type or union might be useful in these cases.

@pciet
Copy link
Contributor Author

pciet commented Dec 15, 2017

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.

@neild
Copy link
Contributor

neild commented Dec 15, 2017

What are the constraints that would be documented? fmt.Print can operate on any value.

@pciet
Copy link
Contributor Author

pciet commented Dec 15, 2017

@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".

@kardianos
Copy link
Contributor

@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.

@neild
Copy link
Contributor

neild commented Dec 18, 2017

@kardianos Apologies, I meant to write database/sql/driver.Value:
https://godoc.org/database/sql/driver#Value

@kardianos
Copy link
Contributor

@neild Same thing still applies, but the docs are outdated now. Shoot. Gotta send a CL.

@gopherbot
Copy link

Change https://golang.org/cl/84636 mentions this issue: database/sql/driver: update Value doc, can be driver supported type

gopherbot pushed a commit that referenced this issue Dec 18, 2017
The driver.Value type may be more then the documented 6 types if the
database driver supports it. Document that fact.

Updates #23077

Change-Id: If7e2112fa61a8cc4e155bb31e94e89b20c607242
Reviewed-on: https://go-review.googlesource.com/84636
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
@kshitij10496
Copy link
Contributor

In addition to @neild's classification above, I would like to share my learnings about the empty interface in the sort package:

Usage:

  • Used as arguments in the 3 function signatures which concern with sorting a user-provided slice of values(namely sort.Slice, sort.SliceStable and sort.SliceIsSorted).

Purpose

  • As mentioned by @pciet above, the motivation behind the use of 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.

@ianlancetaylor
Copy link
Contributor

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.

@golang golang locked and limited conversation to collaborators Mar 27, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

7 participants