Value Conversion, Assignment And Comparison Rules In Go

This article will list all the value assignment, conversion and comparison rules in Go.

Note, the definition of conversion in Go 101 is not exactly the same as Go spec. The conversion in Go spec means explicit conversion. The conversions in Go 101 include both explicit and implicit conversions.

Value Conversion Rules

In Go, if a value v can be explicitly converted to type T, the conversion can be achieveed with the form (T)(v). Sometimes, in particular T is a named type (a defined type or a type alias), the form can be simplified to T(v).

One fact we should know is, when it says a value x can be implicitly converted to a type T, then it means x can also be explicitly converted to type T.

0. The Apparent Conversion Rule

If two types T1 and T2 denote the identical type, then their values can be implicitly converted each other.
For example,

Nothing more to explain about this rule, whether you think this case involves conversions or not.

1. Underlying Type Related Conversion Rules

Given a non-interface value x and a non-interface type T, assume the type of x is Tx,

(Note, the two ignoring struct tags occurrences have taken effect since Go 1.8.)

General example:
package main

func main() {
	// []int, IntSlice and MySlice share
	// the same underlying type: []int
	type IntSlice []int
	type MySlice  []int

	var s  = []int{}
	var is = IntSlice{}
	var ms = MySlice{}
	var x struct{n int `foo`}
	var y struct{n int `bar`}

	// The two lines both fail to compile.
	/*
	is = ms
	ms = is
	*/

	// Must use explicit conversions here.
	is = IntSlice(ms)
	ms = MySlice(is)
	x = struct{n int `foo`}(y)
	y = struct{n int `bar`}(x)

	// Implicit conversions are okay here.
	s = is
	is = s
	s = ms
	ms = s
}
Pointer example:
package main

func main() {
	type MyInt int
	type IntPtr *int
	type MyIntPtr *MyInt

	var pi = new(int) // the type of pi is *int
	var ip IntPtr = pi // ok, same underlying type

	// var _ *MyInt = pi // can't convert implicitly
	var _ = (*MyInt)(pi) // ok, must explicitly

	// Values of *int can't be converted to MyIntPtr directly,
	// but can indirectly.
	/*
	var _ MyIntPtr = pi  // can't convert implicitly
	var _ = MyIntPtr(pi) // can't convert explicitly
	*/
	var _ MyIntPtr = (*MyInt)(pi)  // ok
	var _ = MyIntPtr((*MyInt)(pi)) // ok

	// Values of IntPtr can't be converted to MyIntPtr directly,
	// but can indirectly.
	/*
	var _ MyIntPtr = ip  // can't convert implicitly
	var _ = MyIntPtr(ip) // can't convert explicitly
	*/
	var _ MyIntPtr = (*MyInt)((*int)(ip))  // ok
	var _ = MyIntPtr((*MyInt)((*int)(ip))) // ok
}

2. Channel Specific Conversion Rule

If Tx is a bidirectional channel type, T is a channel type, Tx and T have the identical element type, and either Tx or T is not an defined type, then x can be implicitly converted to T.
Example:
package main

func main() {
	type C chan string
	type C1 chan<- string
	type C2 <-chan string

	var ca C
	var cb chan string

	cb = ca // ok, same underlying type
	ca = cb // ok, same underlying type

	// The 4 lines compile okay for the 2nd rule.
	var _, _ chan<- string = ca, cb // ok
	var _, _ <-chan string = ca, cb // ok
	var _ C1 = cb                   // ok
	var _ C2 = cb                   // ok

	// Values of C can't be converted to C1 and C2 directly.
	/*
	var _ = C1(ca) // compile error
	var _ = C2(ca) // compile error
	*/
	
	// Values of C can be converted to C1 and C2 indirectly.
	var _ = C1((chan<- string)(ca)) // ok
	var _ = C2((<-chan string)(ca)) // ok
	var _ C1 = (chan<- string)(ca)  // ok
	var _ C2 = (<-chan string)(ca)  // ok
}

3. Implementation Related Conversion Rules

Given a value x and an interface type I, assume the type (or the defaut type) of x is Tx, if Tx implements I, then x can be implicitly converted to type I. The conversion result is an interface value (of type I), which stores
Example:
package main

type Filter interface {
	Filte(n int) bool
}

type MultipleFilter struct {
	Divisor int
}

func (mf MultipleFilter) Filte(n int) bool {
	return n % mf.Divisor == 0
}

func main() {
	var mf = MultipleFilter{11}
	// mf's type MultipleFilter implements Filter,
	// so here implicit conversion is okay.
	var filter = mf

	// all types implement interface{} type, so any
	// value can be converted to interface{} implicitly.

	var n int = 123
	var b bool = true
	var s string = "abc"

	var _ interface{} = n
	var _ interface{} = b
	var _ interface{} = s
	var _ interface{} = mf

	// interface type Filter also implements interface{}
	var _ interface{} = filter
}

Many parameters of the functions in standard packages are of type interface{}. As all types implement interface{}, so any value can be passed for such parameters without explict conversion.

4. Type Assertion Conversion Rules

(Type switch can be viewed as a special form of type assertion.)

Given an interface value x and a non-interface type T, assume the interface type of x is Ix, if T implements Ix, then x can be converted to T with type assertion syntax:
Given an interface value x and an interface type I, assume the interface type of x is Ix, then x can be converted to I with type assertion syntax, whether I implements Ix or not:
package main

type I interface {
	f()
}

type T string
func (T) f(){}

func main() {
	var it interface{} = T("abc")
	var is interface{} = "abc"

	// the two are both okay, for T
	// implements both I and interface{}
	var _ T = it.(T)
	var _ I = it.(I)

	// this one is also okay, for
	// string implements interface{}
	var _ string = is.(string)

	// following 3 compile okay but will
	// all panic at run time.
	// _ = is.(I)
	// _ = is.(T)
	// _ = it.(string)
}

5. Untyped Value Conversion Rule

An untyped value can be implicitly converted to type T, if the untyped value can represent as values of type T.
Example:
package main

func main() {
	var _ []int = nil
	var _ map[string]int = nil
	var _ chan string = nil
	var _ func()() = nil
	var _ *bool = nil
	var _ interface{} = nil

	var _ int = 123.0
	var _ float64 = 123
	var _ int32 = 1.23e2
	var _ int8 = 1 + 0i
}

6. Constant Number Conversion Rule

(This rule is some overlapped with the last one.)

Converting a constant yields a typed constant as result.

Given a constant value x and a type T, if x is representable by a value of type T, then x can be explicitly converted to T, in particular if x is an untyped or its type is also T, then x can be implicitly converted to T.

Note, in case of x is a floating-point constant and T is a floating-point type, the conversion result T(x) is the rounded value of x by using IEEE 754 round-to-even rules, but with an IEEE -0.0 further rounded to an unsigned 0.0.

Example:
package main

func main() {
	const I = 123
	const I1, I2 int8 = 0x7F, -0x80
	const I3, I4 int8 = I, 0.0

	const F = 0.123456789
	const F32 float32 = F
	const F32b float32 = I
	const F64 float64 = F
	const F64b = float64(I3)

	const C1, C2 complex64 = F, I
	const I5 = int(C2)
}

7. Non-Constant Number Conversion Rules

Non-constant floating-point and integer values can be explicitly converted to any floating-point and integer types.
Non-constant complex values can be explicitly converted to any complex types.
Note,
package main

import "fmt"

func main() {
	var a, b = 1.6, -1.6 // both are float64
	fmt.Println(int(a), int(b)) // 1 -1

	var i, j int16 = 0x7FFF, -0x8000
	fmt.Println(int8(i), uint16(j)) // -1 32768

	var c1 complex64 = 1 + 2i
	var _ = complex128(c1)
}

8. String Related Conversion Rules

If the type (or default type) of a value is an interger type, then the value can be explicitly converted to string types.
A string value can be explicitly converted to a slice type whose underlying type is []byte (a.k.a., []uint8), and vice versa.
A string value can be explicitly converted to a slice type whose underlying type is []rune (a.k.a., []int32), and vice versa.

Please read strings in Go for details and examples.

9. Unsafe Pointers Related Conversion Rules

Above conversion rules can be broken by unsafe pointers related conversion rules.

A pointer value of any type can be converted to a type whose underlying type is unsafe.Pointer, and vice versa.
An uintptr value can be converted to a type whose underlying type is unsafe.Pointer, and vice versa.

Please read type-unsafe pointers in Go for details and examples.

Value Assignment Rules

Assignments can be viewed as implicit conversions.

In an assignment, if the target value is a new declared variable without given a type in code, then the type of the new variable is

Implicit conversion rules are listed among the conversion rules in the last section.

In addition, any value can be assigned to the blank identifier _.

Note, parameter passing, channel value send and receive operations are all value assignment involved.

For most value assignments, the direct part of each source value is copied to the direct part of the corresponding destination value, except that destination value is an interface value.

In an assignment, if the destination value is an interface value but the source value is not, then (the direct part of) the source value will be copied and the copy result will be stored in the destination interface value as dynamic value. The standard compiler makes some special treatments here if the source value is not a pointer value. If the source value is not a pointer value, the direct part of the source value will be copied and a pointer to the (hidden and immutable) copy will be stored in the destination interface value.

In an assignment, if both the destination and source values are interface values, then (the direct part of) the dynamic value of the source interface value will be copied and the copy result will be stored in the destination interface value as dynamic value. The standard compiler makes some optimizations here so that a pointer is always copied in the process.

Value Comparison Rules

Go specification states:

In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.

So, the comparison rule is much like the assignment rule. In other words, two values are comparable if one can be implicitly converted to the type of the other. Right? Almost, for there is an exception for the above basic comparison rule.

If one of the two operands in a comparison is an interface value, and the other operand is a non-interface value of an uncomparable type (which should implement the former operand interface type), then the comparison is invalid, even if the non-interface value can be implicitly converted to the interface type.

Note, although generally, values of slice/map/function types don't support comparisons, they can be compared with untyped nil values (a.k.a., bare nil identifiers).

The above described basic rule doesn't cover all circumstances. What about if both of the two operands in a comparison are untyped (constant) values? The additional rules are simple:

The results of comparing two untyped values obey intuition.

Note, an untyped nil value can't be compared with another untyped nil value.

Any comparison results an untyped boolean value.

Examples:
package main

// Some variables of uncomparable types.
var s []int
var m map[int]int
var f func()()
var t struct {x []int}
var a [5]map[int]int

func main() {
	// The following lines fail to compile.
	/*
	_ = s == s
	_ = m == m
	_ = f == f
	_ = t == t
	_ = a == a
	_ = nil == nil
	_ = s == interface{}(nil)
	_ = m == interface{}(nil)
	_ = f == interface{}(nil)
	*/

	// The following lines compile okay.
	_ = s == nil
	_ = m == nil
	_ = f == nil
	_ = 123 == interface{}(nil)
	_ = true == interface{}(nil)
	_ = "abc" == interface{}(nil)
}

How Are Two Values Compared

Assume two values are comparable, and they have the same type T (if they have different types, one of them must be implicitly convertible to the type of the other).
  1. If T is a boolean type, then the two values are equal only if they are both true or both false.
  2. If T is an integer type, then the two values are equal only if they have the same bytes representations in memory.
  3. If T is a floating-point type, then the two values are equal only if any of the following conditions is satisfied:
    • they are both +Inf.
    • they are both -Inf.
    • they each is either -0.0 or +0.0.
    • they are both not NaN and they have the same bytes representations in memory.
  4. If T is a complex type, then the two values are equal only if their real parts (as floating-point values) and imaginary parts (as floating-point values) are both equal.
  5. If T is a pointer type (either safe or unsafe), then the two values are equal only if they both reference the same value or they are both nil pointers.
  6. If T is a channel type, the two channel values are equal if they both reference the same underlying channel structure or they are both nil channels.
  7. If T is a struct type, then each pair of the corresponding fields of the two struct values will be compared.
  8. If T is an array type, then each pair of the corresponding elements of the two array values will be compared.
  9. If T is an interface type, please read how two interface values are compared.
  10. If T is a string type, please read how two string values are compared.

Please note, comparing two interfaces with the same uncomparable dynamic type produces a panic. A sub-comparison of the first three cases mentioned above may also be an interface value comparison. So comparisons of the first three cases may be also possible to produce panics.

Here is an example in which some panics will occur in comparisons.

package main

func main() {
	type T struct {
		a interface{}
		b int
	}
	var x interface{} = []int{}
	var y = T{a: x}
	var z = [3]T{}

	// Each of the following line can produce a panic.
	_ = x == x
	_ = y == y
	_ = z == z
}

Please note, the two code lines involved z compile okay for the go build and go install commands in any version of Go SDK, but fail to compile for the go run command in Go SDK 1.9 and 1.10. This is a known bug of the standard Go compiler 1.9 and 1.10.


The Go 101 project is hosted on both Github and Gitlab. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.

Support Go 101 by playing Tapir's games. Cryptocurrency donations are also welcome:
Bitcoin: 1xucQbv5jujFPPwhyg395ri5yV71hx9g9
Ethereum: 0x5dc4aa2c2bbfaadae373dadcfca11b3358912212