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

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

Syntax/Semantics Exceptions in Go

This article will list all kinds of syntax/semantics exceptions in Go. Some of these exceptions are syntactic sugars to make programming convenient, some are caused built-in generic privileges, some exists for history reasons, and some exists for other reasons in logic.

Nested function calls

The basic rule:
If the number of the return results of a function call is not zero, and the return results can be used as the whole arguments of another function call, then the former function call can be nested in the latter function call, the former nested call can't mix up with other arguments of the latter nesting call.
Sugar:
If a function call returns exactly one result, then the function call can be always be used as a single-value argument in other function calls, the single-result function call can mix up with other arguments of the nesting function calls.
Example:
package main

import (
	"fmt"
)

func f0() float64 {return 1}
func f1() (float64, float64) {return 1, 2}
func f2(float64, float64) {}
func f3(float64, float64, float64) {}
func f4()(x, y []int) {return}
func f5()(x map[int]int, y int) {return}

type I interface {m()(float64, float64)}
type T struct{}
func (T) m()(float64, float64) {return 1, 2}

func main() {
	// These lines compile okay.
	f2(f0(), 123)
	f2(f1())
	fmt.Println(f1())
	_ = complex(f1())
	_ = complex(T{}.m())
	f2(I(T{}).m())

	// These lines don't compile.
	/*
	f3(123, f1())
	f3(f1(), 123)
	*/

	// This line compiles okay only since
	// Go Toolchain 1.15.
	println(f1())

	// The following 3 lines compile okay
	// only since Go Toolchain 1.13.
	copy(f4())
	delete(f5())
	_ = complex(I(T{}).m())
}

Select struct fields

The basic rule:
Pointer values have no fields.
Sugar:
We can select the fields of a struct value through pointers of the struct value.
Example:
package main

type T struct {
	x int
}

func main() {
	var t T
	var p = &t

	p.x *= 2
	// The above line is a sugar of the following line.
	(*p).x *= 2
}

Receiver arguments of method calls

The basic rule:
The methods explicitly declared for type *T are not methods of type T for sure.
Sugar:
Although the methods explicitly declared for type *T are not methods of type T, addressable values of type T can be used as the receiver arguments of calls to these methods.
Example:
package main

type T struct {
	x int
}

func (pt *T) Double() {
	pt.x *= 2
}

func main() {
	// T{3}.Double() // This line fails to compile.

	var t = T{3}

	t.Double() // t.x == 6 now
	// The above line is a sugar of the following line.
	(&t).Double() // t.x == 12 now
}

Take addresses of composite literal values

The basic rule:
Literal values are unaddressable and unaddressable values can't be taken addresses.
Sugar:
Although composite literal values are not addressable, they can be taken addresses explicitly.

Please read structs and containers for details.

Selectors on named one-Level pointers

The basic rule:
Generally, selectors can't be used on values of named pointer types.
Sugar:
If x is a value of a named one-level pointer type, and selector (*x).f is a legal selector, then the x.f is also a legal selector, it can be viewed as a shorthand of (*x).f.

Selectors can never be used on values of multi-level pointer types, no matter whether the multi-level pointer types are named or not.

Exception of the sugar:
The sugar is only valid if f denotes a struct field, it is not valid if f denotes a method.
Example:
package main

type T struct {
	x int
}

func (T) y() {
}

type P *T
type PP **T // a multi-level pointer type

func main() {
	var t T
	var p P = &t
	var pt = &t   // type of pt is *T
	var ppt = &pt // type of ppt is **T
	var pp PP = ppt
	_ = pp

	_ = (*p).x // legal
	_ = p.x    // also legal (for x is a field)

	_ = (*p).y // legal
	// _ = p.y // illegal (for y is a method)

	// Following ones are all illegal.
	/*
	_ = ppt.x
	_ = ppt.y
	_ = pp.x
	_ = pp.y
	*/
}

The addressability of a container and its elements

The basic rule:
If a container is addressable, then its elements are also addressable.
Exception:
Elements of a map are always unaddressable, even if the map itself is addressable.
Sugar:
Elements of a slice are always addressable, even if the slice itself is not addressable.
Example:
package main

func main() {
	var m = map[string]int{"abc": 123}
	_ = &m // okay

	// The exception:
	// p = &m["abc"] // error: map elements are unaddressable

	// The sugar:
	f := func() []int { // return results are unaddressable
		return []int{0, 1, 2}
	}
	// _ = &f() // error: f() is unaddressable
	_ = &f()[2] // okay
}

Modify unaddressable values

The basic rule:
Unaddressable values can't be modified. In other words, unaddressable values shouldn't appear in assignments as destination values.
Exception:
Although map element values are unaddressable, they can be modified and appear in assignments as destination values. (But map elements can't be modified partially, they can only be overwritten wholly, a.k.a., replaced.)
Example:
package main

func main() {
	type T struct {
		x int
	}

	var mt = map[string]T{"abc": {123}}
	// Map elements are unaddressable.
	// _ = &mt["abc"] // error
	// Partial modification is not allowed.
	// mt["abc"].x = 456 // error
	// It is ok to replace a map element as a whole.
	mt["abc"] = T{x: 789}
}

Function Parameters

The basic rule:
Each parameter is a value of some type.
Exception:
The first parameters of the built-in make and new functions are types.

Function names in one package

The basic rule:
Names of declared functions can't be duplicated in one package.
Exception:
There can be multiple functions declared with names as init (and types as func()).

Function calls

The basic rule:
Functions whose names are not the blank identifier can be called in user code.
Exception:
init functions can't be called in user code.

Functions being used as values

The basic rule:
Declared functions can be used as function values.
Exception:
init functions can not be used as function values.
Example:
package main

import "fmt"
import "unsafe"

func init() {}

func main() {
	// These ones are okay.
	var _ = main
	var _ = fmt.Println

	// This one fails to compile.
	var _ = init
}

The manners of type argument passing

The basic rule:
In a type argument list, all type arguments are enclosed in square brackets and they are seperated by commas in the list.
Exception:
The manners of type argument lists passed to built-in generic type instantiations vary in different forms. Each key type argument of instantiated map types is individually enclosed in square brackets. Other type arguments are not enclosed. The type argument of an instantiation of built-in new generic function is enclosed in parentheses. The type argument of an instantiation of the built-in make generic function is mixed with value arguments and these type and value arguments are enclosed in parentheses.

Discard return values of function calls

The basic rule:
The return values of a function call can be discarded altogether.
Exception:
The return values of calls to the built-in functions which are documented in the builtin and unsafe standard packages, can't be discarded, if the called function has return results.
Exception in exception:
The return values of a call to the built-in copy and recover functions can be all discarded, even if the two functions have return results.

Declared variables

The basic rule:
Declared variables are always addressable.
Exception:
The predeclared nil variable is not addressable.
So, nil is an immutable variable.

Argument passing

The basic rule:
An argument can be passed to the corresponding function parameter only if the argument is assignable to the corresponding function parameter type.
Sugar:
If the first slice argument of a copy and append function call is a byte slice, then the second argument can be a string, whereas a string value is not assignable to the second parameter type (also a byte slice). (For an append call, assume the second argument is passed with the form arg....)
Example:
package main

func main() {
	var bs = []byte{1, 2, 3}
	var s = "xyz"

	copy(bs, s)
	// The above line is a sugar (and an optimization)
	// for the following line.
	copy(bs, []byte(s))

	bs = append(bs, s...)
	// The above line is a sugar (and an optimization)
	// for the following line.
	bs = append(bs, []byte(s)...)
}

Comparisons

The basic rule:
Map, slice and function types don't support comparison.
Exception:
Map, slice and function values can be compared to the predeclared untyped nil identifier.
Example:
package main

func main() {
	var s1 = []int{1, 2, 3}
	var s2 = []int{7, 8, 9}
	//_ = s1 == s2 // error: slice values can't be compared
	_ = s1 == nil // ok
	_ = s2 == nil // ok

	var m1 = map[string]int{}
	var m2 = m1
	// _ = m1 == m2 // error: map values can't be compared
	_ = m1 == nil
	_ = m2 == nil

	var f1 = func(){}
	var f2 = f1
	// _ = f1 == f2 // error: functions can't be compared
	_ = f1 == nil
	_ = f2 == nil
}

Comparisons 2

The basic rule:
If a value can be implicitly converted to a comparable type, then the value can be compared to the values of the comparable type.
Exception:
The values of a non-interface incomparable type can't be compared to values of an interface type, even if the non-interface incomparable type implements the interface type (so values of the non-interface incomparable type can be implicitly converted to the interface type).
Please read comparison rules for examples.

Blank composite literals

The basic rule:
If the values of a type T can be represented with composite literals, then T{} is its zero value.
Exception:
For a map or a slice type T, T{} isn't its zero value. Its zero value is represented with nil.
Example:
package main

import "fmt"

func main() {
	// new(T) returns the address of a zero value of type T.

	type T0 struct {
		x int
	}
	fmt.Println( T0{} == *new(T0) ) // true
	type T1 [5]int
	fmt.Println( T1{} == *new(T1) ) // true

	type T2 []int
	fmt.Println( T2{} == nil ) // false
	type T3 map[int]int
	fmt.Println( T3{} == nil ) // false
}

Container element iterations

The basic rule:
Only container values can be ranged, the iterated values are container elements. The element key/index will also be returned alongside of each iterated element.
Exception 1:
The iterated values are runes if the ranged containers are strings, instead of the byte elements of strings.
Exception 2:
The element index (order) will not be returned alongside of each iterated element when iterating channels.
Sugar:
Array pointers can also be ranged to iterate array elements, though pointers are not containers.

Methods of built-in types

The basic rule:
Generally, built-in types have no methods.
Exception:
The built-in error type has a Error() string method.

Types of values

The basic rule:
Each value has either a type or a default type.
Exception:
Untyped nil has neither a type nor a default type.

Constant values

The basic rule:
Constant values never change. Constants can be assigned to variables.
Exception:
Predeclared iota is a built-in constant which is bound with 0, but its value is not constant. Its value will start from 0 and increase one constant specification by constant specification in a constant declaration, though the increments happen at compile time.
Exception 2:
iota can only be used within constant declarations. It can't be assigned to variables in variable declarations.

Behavior change caused by discarding the optional evaluation results of expressions

The basic rule:
Whether or not the optional evaluation result of an expression presents will not affect program behavior.
Exception:
Missing the optional result value in a type assertion will make current goroutine panic if the type assertion fails.
Example:
package main

func main() {
	var ok bool

	var m = map[int]int{}
	_, ok = m[123] // will not panic
	_ = m[123]     // will not panic

	var c = make(chan int, 2)
	c <- 123
	close(c)
	_, ok = <-c // will not panic
	_ = <-c     // will not panic

	var v interface{} = "abc"
	_, ok = v.(int) // will not panic
	_ = v.(int)     // will panic!
}

else keyword followed by a code block

The basic rule:
The else keyword must be followed by an explicit code block {...}.
Sugar:
The else keyword may be followed by a if code block (which is implicit).
For example, in the following code, function foo compiles okay, but function bar doesn't.
func f() []int {
	return nil
}

func foo() {
	if vs := f(); len(vs) == 0 {
	} else if len(vs) == 1 {
	}

	if vs := f(); len(vs) == 0 {
	} else {
		switch {
		case len(vs) == 1:
		default:
		}
	}
	
	if vs := f(); len(vs) == 0 {
	} else {
		for _, _ = range vs {
		}
	}
}

func bar() {
	if vs := f(); len(vs) == 0 {
	} else switch { // error
	case len(vs) == 1:
	default:
	}
	
	if vs := f(); len(vs) == 0 {
	} else for _, _ = range vs { // error
	}
}

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, 세가지 미니 게임이 있는 캐주얼 액션 게임
페이팔을 통한 개인 기부도 환영합니다.

색인: