Type check the empty interface{}

Static type checker for interface{} with a type list

This is an experiment.

  • This is a tool that performs a static type check on values of type interface{}.
  • You specify the types in a special comment, eg // #type T1, T2
  • Internally, the implementation is based on go2go's Sum type. Go2go and this experiment have different concerns: go2go is about generic functions and type parameters, this experiment is about sum types.
  • For an alternative solution see the siadat/group-interface pattern.

Demo

Without type checking:

/*    */  package main
/*    */
/*    */  type Numeric interface{}
/*    */  
/*    */  func main() {
/*    */  	var n Numeric
/* OK */  	n = 3
/* OK */  	n = 3.14
/* OK */  	n = "abcd"
/*    */  	_ = n
/*    */  }

With type checking:

/*     */  package main
/*     */
/*     */  type Numeric interface{
/* --> */  	// #type int, float64
/*     */  }
/*     */  
/*     */  func main() {
/*     */  	var n Numeric
/* OK  */  	n = 3
/* OK  */  	n = 3.14
/* ERR */  	n = "abcd"
/*     */  	_ = n
/*     */  }

Execute the checker to get the error:

$ interface-type-check .
testfile.go:10:6: cannot use "bad value" (constant of type string) as Numeric value in variable declaration: mismatching sum type (have string, want a type in interface{type int, float64})

Download

Prebuilt binaries are available here as well as in the release page.

Build

git clone https://github.com/siadat/interface-type-check
cd interface-type-check
make test build

Checks

Given the declaration:

type Numeric interface{
	// #type int, float64
}

The following checks are performed:

var number Numeric = "abc" // CHECK ERR: expected int or float
_, _ = number.(string)     // CHECK ERR: string not allowed
switch number.(type) {
case string:               // CHECK ERR: string not allowed
case float:
}
switch number.(type) {     // CHECK ERR: missing case for int
case float:
}
switch number.(type) {     // CHECK ERR: missing case for nil
case float:
case int:
}

More examples: fork/src/types/examples/sum.go2

Experiment: json.Token

All supported types of encoding/json.Token are known, as documented here:

// A Token holds a value of one of these types:
//
//	Delim, for the four JSON delimiters [ ] { }
//	bool, for JSON booleans
//	float64, for JSON numbers
//	Number, for JSON numbers
//	string, for JSON string literals
//	nil, for JSON null
//
type Token interface{}

Adding the #type comment, it would look like this:

type Token interface {
	// #type Delim, bool, float64, Number, string
}

That's all we need to be able to use the checker.

Experiment: sql.Scanner

database/sql.Scanner is also defined as an empty interface whose possible types are known.

Before:

// Scanner is an interface used by Scan.
type Scanner interface {
	// Scan assigns a value from a database driver.
	//
	// The src value will be of one of the following types:
	//
	//    int64
	//    float64
	//    bool
	//    []byte
	//    string
	//    time.Time
	//    nil - for NULL values
	//
	Scan(src interface{}) error
}

After:

// Scanner is an interface used by Scan.
type Scanner interface {
	Scan(src SourceType) error
}

type SourceType interface {
	// #type int64, float64, bool, []byte, string, time.Time
}

Experiment: net.IP

The standard library defines one net.IP type for both IPv4 and IPv6 IPs:

// An IP is a single IP address, a slice of bytes.
// Functions in this package accept either 4-byte (IPv4)
// or 16-byte (IPv6) slices as input.
type IP []byte

This type has a String() function, which relies on runtime checks to detect the version of the IP here:

if p4 := p.To4(); len(p4) == IPv4len { ...

There are very good reasons to use a simple []byte data structure for the IPs. I am not suggesting that this code should change. I am only running tiny hypothetical experiments. With that in mind, let's write it using // #type:

type IPv4 [4]byte
type IPv6 [16]byte

type IP interface {
	// #type IPv4, IPv6
}

func version(ip IP) int {
	switch ip.(type) {
	case IPv4: return 4
	case IPv6: return 6
	case nil:  panic("ip is nil")
	}
}

Experiment: a hypothetical connection object

The Connecting type has a retry field:

type Connected    struct{}
type Disconnected struct{}
type Connecting   struct{ rety int }

type Connection interface {
	// #type Connected, Disconnected, Connecting
}

func log(conn Connection) int {
	switch c := conn.(type) {
	case Connected:    fmt.Println("Connected")
	case Disconnected: fmt.Println("Disconnected")
	case Connecting:   fmt.Println("Connecting, retry:", c.retry)
	case nil:          panic("conn is nil")
	}
}

When to use / When not to use

Empty interfaces are used when we want to store variables of different types which don't implement a common interface.

There are two general use cases of an empty interface:

  1. supported types are unknown (eg json.Marshal)
  2. supported types are known (eg json.Token)

Don't use if:

You should not use this checker for 1. Sometimes we do not have prior knowledge about the expected types. For example, json.Marshal(v interface{}) is designed to accept structs of any type. This function uses reflect to gather information it needs about the type of v. In this case, it is not possible to list all the supported types.

Use if:

You could consider using it, when all the types you support are known at the type of writing your code.

This is particularly useful when the types are primitives (eg int), where we have to create a new wrapper type (eg type Int int) and implement a non-empty interface on it.

Go2's type list

This tool is designed to work with code written in the current versions of Go (ie Go1). The current design draft of Go2 includes the type list:

type Numeric interface {
	type int, float64
}

At the moment, the type list is intended for function type parameters only.

interface type for variable cannot contain type constraints

The draft notes:

Interface types with type lists may only be used as constraints on type parameters. They may not be used as ordinary interface types. The same is true of the predeclared interface type comparable.

This restriction may be lifted in future language versions. An interface type with a type list may be useful as a form of sum type, albeit one that can have the value nil. Some alternative syntax would likely be required to match on identical types rather than on underlying types; perhaps type ==. For now, this is not permitted.

The highlight section is what this experiment addresses via an external type checking tool.

You might think of this tool as an experiment to see whether a sum type would be a valuable addition to the language.

Implementation

  • This experiment is built on top of the dev.go2go branch and uses types.Sum. See diff.
  • A few more test examples are added to types/examples.

Limitations

  • Only single line comments are implemented, ie // #type T1, T2
  • Zero-value for an interface is nil. Several approaches come to mind:
    • allow nil values.
    • allow nil values, but fail if type switch statements don't include nil (what we do in this checker).
    • track all initializations/assignments/etc of the interfaces with types and fail if they are nil.
    • change the zero-value of an interface with a type list to be the zero-value of its first type (or some type chosen by the programmer).

Contribute

Do any of these:

  • Download a binary, or build from source.
  • Report issues. You will most likely run into problems, because this is a new project.
  • Use it! Let me know what you use it for.
  • Search for TODOs in the code.
  • Implement missing features.
Owner
Sina Siadat
Techlead at Alibaba. Previously at Cafebazaar, Sariina, Aylien, Nokia. Interested in programming languages and distributed systems.
Sina Siadat
Similar Resources

Check-load - Simple cross-platform load average check

Sensu load average check Table of Contents Overview Usage examples Configuration

Jun 16, 2022

A go module supply Java-Like generic stream programming (while do type check at runtime)

gostream A go module supplying Java-Like generic stream programming (while do type check at runtime) Using Get a Stream To get a Stream, using SliceSt

Jan 16, 2022

[TOOL, CLI] - Filter and examine Go type structures, interfaces and their transitive dependencies and relationships. Export structural types as TypeScript value object or bare type representations.

typex Examine Go types and their transitive dependencies. Export results as TypeScript value objects (or types) declaration. Installation go get -u gi

Dec 6, 2022

Go generator to copy values from type to type and fields from struct to struct. Copier without reflection.

Copygen is a command-line code generator that generates type-to-type and field-to-field struct code without adding any reflection or dependenc

Dec 29, 2022

convert JSON of a specific format to a type structure(Typescript type or more)

json2type convert JSON of a specific format to a type structure(Typescript type or more) Quick Start CLI Install use go tool install go install github

Mar 28, 2022

A drop-in replacement to any Writer type, which also calculates a hash using the provided hash type.

writehasher A drop-in replacement to any Writer type, which also calculates a hash using the provided hash type. Example package main import ( "fmt"

Jan 10, 2022

efaceconv - Code generation tool for high performance conversion from interface{} to immutable type without allocations.

efaceconv High performance conversion from interface{} to immutable types without additional allocations This is tool for go generate and common lib (

May 14, 2022

A faster method to get elements from an interface (Struct or Slice type) for Go.

A faster method to get elements from an interface (Struct or Slice type) for Go.

May 13, 2022

Generate type-safe Go converters by simply defining an interface

goverter a "type-safe Go converter" generator goverter is a tool for creating type-safe converters. All you have to do is create an interface and exec

Jan 4, 2023

dont-interface calculates how many interface{} are declared or used in your project?

dont-interface calculates how many interface{} are declared or used in your project?

Jun 9, 2022

Go library to interface with NEAR nodes' JSON-RPC interface

StreamingFast Solana library for Go Go library to interface with NEAR nodes' JSON-RPC interface Contributing Issues and PR in this repo related strict

Nov 9, 2021

Recursively searches a map[string]interface{} structure for another map[string]interface{} structure

msirecurse Recursively searches a map[string]interface{} structure for existence of a map[string]interface{} structure Motivation I wrote this package

Mar 3, 2022

An easy to use, extensible health check library for Go applications.

Try browsing the code on Sourcegraph! Go Health Check An easy to use, extensible health check library for Go applications. Table of Contents Example M

Dec 30, 2022

An simple, easily extensible and concurrent health-check library for Go services

An simple, easily extensible and concurrent health-check library for Go services

Healthcheck A simple and extensible RESTful Healthcheck API implementation for Go services. Health provides an http.Handlefunc for use as a healthchec

Dec 30, 2022

Provide check digit algorithms and calculators written in Go

checkdigit About Provide check digit algorithms and calculators written by Go. Provided methods Algorithms Luhn Verhoeff Damm Calculators ISBN-10 ISBN

Dec 17, 2022

Simple application written in Go that combines two wordlists and a list of TLDs to form domain names and check if they are already registered.

Domainerator Domainerator was my first Go application. It combines two wordlists (prefixes and suffixes) and a list of TLDs to form domain names and c

Sep 16, 2022

A small CLI tool to check connection from a local machine to a remote target in various protocols.

CHK chk is a small CLI tool to check connection from a local machine to a remote target in various protocols.

Oct 10, 2022

A full-featured license tool to check and fix license headers and resolve dependencies' licenses.

A full-featured license tool to check and fix license headers and resolve dependencies' licenses.

SkyWalking Eyes A full-featured license tool to check and fix license headers and resolve dependencies' licenses. Usage You can use License-Eye in Git

Dec 26, 2022

Periodically collect data about my Twitter account and check in to github to preserve an audit trail.

Twitter audit trail backup This repository backs up my follower list, following list, blocked accounts list and muted accounts list periodically using

Dec 28, 2022
A Go linter to check that errors from external packages are wrapped

Wrapcheck A simple Go linter to check that errors from external packages are wrapped during return to help identify the error source during debugging.

Dec 27, 2022
Remove unnecessary type conversions from Go source

About The unconvert program analyzes Go packages to identify unnecessary type conversions; i.e., expressions T(x) where x already has type T. Install

Nov 28, 2022
May 8, 2022
A better ORM for Go, based on non-empty interfaces and code generation.

reform A better ORM for Go and database/sql. It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as oppose

Dec 31, 2022
A better ORM for Go, based on non-empty interfaces and code generation.
A better ORM for Go, based on non-empty interfaces and code generation.

A better ORM for Go and database/sql. It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as opposed to interface{}, type system sidestepping, and runtime reflection. It will be kept simple.

Dec 29, 2022
Allows parsing CSV files into custom structs and implements required fields that can't be empty

Welcome to Go Custom CSV Parser ?? Allows parsing CSV files into custom structs and implements required fields that can't be empty ?? Homepage Install

Nov 9, 2021
Empty an S3 bucket as fast as possible 💨

s3-destroyer Iteratively calls ListObjects, add objects keys to a buffer and calls DeleteObject in goroutines. Usage -access-key string s3 a

Dec 13, 2021
Golang: unify nil and empty slices and maps

unifynil, unify nil and empty slices and maps in Golang Empty slices and maps can be nil or not nil in Go. It may become a nightmare in tests and JSON

Jan 16, 2022
check-cert: Go-based tooling to check/verify certs

check-cert: Go-based tooling to check/verify certs

Dec 6, 2022
Check-location - A golang service to check user location using their IP address

this is a golang service to check user location using their IP address. The purp

Aug 29, 2022