현재 3권의 신간들인 Go Optimizations 101, Go Details & Tips 101Go Generics 101이 출간되어 있습니다. Leanpub 서점에서 번들을 모두 구입하시는 방법이 비용 대비 효율이 가장 좋습니다.

Go에 대한 많은 정보들과 Go 101 책들의 최신 소식을 얻으시려면 Go 101 트위터 계정인 @Go100and1을 팔로잉 해주세요.

Value Conversion, Assignment and Comparison Rules in Go

This article will list all the value comparison, conversion and comparison rules in Go. Please note that type parameter types (used frequently in custom generics) are deliberately ignored in the descriptions of conversion, assignability and comparison rules. In other words, this book doesn't consider the situations in which custom generics are involved.

Value Conversion Rules

In Go, if a value v can be explicitly converted to type T, the conversion can be represented as the form (T)(v). For most cases, in particular T is a type name (an identifier), 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.

1. the apparent conversion rule

If two types denote the identical type, then their values can be implicitly converted to either type of the two.
For example,

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

2. 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.)

An example:
package main

func main() {
	// []int, IntSlice and MySlice share
	// the same underlying type: []int
	type IntSlice []int
	type MySlice  []int
	type Foo = struct{n int `foo`}
	type Bar = struct{n int `bar`}

	var s  = []int{}
	var is = IntSlice{}
	var ms = MySlice{}
	var x map[Bar]Foo
	var y map[Foo]Bar

	// The two implicit conversions both doesn't work.
	/*
	is = ms // error
	ms = is // error
	*/

	// Must use explicit conversions here.
	is = IntSlice(ms)
	ms = MySlice(is)
	x = map[Bar]Foo(y)
	y = map[Foo]Bar(x)

	// Implicit conversions are okay here.
	s = is
	is = s
	s = ms
	ms = s
}

Pointer related conversion example:
package main

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

	var pi = new(int)  // the type of pi is *int
	// ip and pi have the same underlying type,
	// and the type of pi is unnamed, so
	// the implicit conversion works.
	var ip IntPtr = pi

	// 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
}

3. channel specific conversion rule

Given a channel value x, assume its type Tx is a bidirectional channel type, T is also a channel type (bidirectional or not). If Tx and T have the identical element type, and either Tx or T is an unnamed 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 this 3rd 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
}

4. interface implementation related conversion rules

Given a value x and an interface type I, if the type (or the default type) of x is Tx and Tx implements I, then x can be implicitly converted to type I. The conversion result is an interface value (of type I), which boxes

Please read interfaces in Go for details and examples.

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. constants conversion rule

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

Generally, converting a constant still yields a constant as result. (Except converting a constant string to byte slice or rune slice described in the below 8th rules.)

Given a constant value x and a type T, if x is representable as a value of type T, then x can be explicitly converted to T. In particular if x is an untyped value, then x can be implicitly converted to T.
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) // must be explicitly

	const C1, C2 complex64 = F, I
	const I5 = int(C2) // must be explicitly
}

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, An example:
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 integer 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. slices related conversions

Since Go 1.17, a slice may be converted to an array pointer. In such a conversion, if the length of the base array type of the pointer type is larger than the length of the slice, a panic occurs.

Here is an example.

Since Go 1.20, a slice may be converted to an array. In such a conversion, if the length of the array type is larger than the length of the slice, a panic occurs.

Here is an example.

10. unsafe pointers related conversion rules

A pointer value of any type can be explicitly converted to a type whose underlying type is unsafe.Pointer, and vice versa.
An uintptr value can be explicitly 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. Implicit conversion rules are listed among all conversion rules in the last section.

Besides these rules, the destination values in assignments must be addressable values, map index expressions, or the blank identifier.

In an assignment, the source value is copied to the destination value. Precisely speaking, the direct part of the source value is copied to the destination value.

Note, parameter passing and result returning are both value assignments actually.

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 of them can be implicitly converted to the type of the other. Right? Almost, for there is another rule which has a higher priority than the above basic comparison rule.

If both of the two operands in a comparison are typed, then their types must be a comparable type.

By the above rule, if an incomparable type (which must be a non-interface type) implements an interface type, then it is illegal to compare values of the two types, even if values of the former (non-interface) type can be implicitly converted the latter (interface) type.

Note, although 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 rules don't cover all cases. 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 numeric values obey intuition.

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

Any comparison results in an untyped boolean value.

The following example shows some incomparable types related comparisons.
package main

// Some variables of incomparable 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. Here we don't consider the cases in which both the two values are untyped.)
  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 representation 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.
    • each of them 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 the memory addresses stored in them are equal.
  6. If T is a channel type, the two channel values are equal if they both reference the same underlying internal channel structure value 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 incomparable dynamic type produces a panic. The following 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{{a: y}}

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


Index↡

The Go 101 프로젝트는 Github 에서 호스팅됩니다. 오타, 문법 오류, 부정확한 표현, 설명 결함, 코드 버그, 끊어진 링크와 같은 모든 종류의 실수에 대한 수정 사항을 제출하여 Go 101을 개선을 돕는 것은 언제나 환영합니다.

주기적으로 Go에 대한 깊이 있는 정보를 얻고 싶다면 Go 101의 공식 트위터 계정인 @go100and1을 팔로우하거나 Go 101 슬랙 채널에j가입해주세요.

이 책의 디지털 버전은 아래와 같은 곳을 통해서 구매할 수 있습니다.
Go 101의 저자인 Tapir는 2016년 7월부터 Go 101 시리즈 책들을 집필하고 go101.org 웹사이트를 유지 관리하고 있습니다. 새로운 콘텐츠는 책과 웹사이트에 수시로 추가될 예정입니다. Tapir는 인디 게임 개발자이기도 합니다. Tapir의 게임을 플레이하여 Go 101을 지원할 수도 있습니다. (안드로이드와 아이폰/아이패드용):
  • Color Infection (★★★★★), 140개 이상의 단계로 이루어진 물리 기반의 캐주얼 퍼즐 게임
  • Rectangle Pushers (★★★★★), 2가지 모드와 104개 이상의 단계로 이루어진 캐주얼 퍼즐 게임
  • Let's Play With Particles, 세가지 미니 게임이 있는 캐주얼 액션 게임
페이팔을 통한 개인 기부도 환영합니다.

색인: