Function declarations and calls have been explained before. The current article will touch more function related concepts and details in Go.
In fact, function is one kind of first-class citizen types in Go. In other words, we can use functions as values. Although Go is a static language, Go functions are very flexible. The feeling of using Go functions is much like using many dynamic languages.
There are some built-in functions in Go.
These functions are documented in builtin
and unsafe
standard code packages.
Built-in functions have several differences from custom functions.
These differences will be mentioned below.
The literal of a function type is composed of the func
keyword
and a function signature literal.
A function signature is composed of two type list,
one is the input parameter type list, the other is the output result type lists.
Parameter and result names can appear in function type and signature literals,
but the names are not important.
In practice, the func
keyword can be presented in signature literals, or not.
For this reason, we can think function type and function signature as the same concept.
func (a int, b string, c string) (x int, y int, z bool)
From the article function declarations and calls,
we have learned that consecutive parameters and results of the same type can be declared together.
So the above literal is equivalent to
func (a int, b, c string) (x, y int, z bool)
As parameter names and result names are not important in the literals
(as long as there are no duplicate non-blank names),
the above ones are equivalent to the following one.
func (x int, y, z string) (a, b int, c bool)
Variable (parameter and result) names can be blank identifier _
.
The above ones are equivalent to the following one.
func (_ int, _, _ string) (_, _ int, _ bool)
The parameter names must be either all present or all absent (anonymous).
The same rule is for result names.
The above ones are equivalent to the following ones.
func (int, string, string) (int, int, bool) // the standard form
func (a int, b string, c string) (int, int, bool)
func (x int, _ string, z string) (int, int, bool)
func (int, string, string) (x int, y int, z bool)
func (int, string, string) (a int, b int, _ bool)
All of the above function type literals denote the same (unnamed) function type.
Each parameter list must be enclosed in a()
in a literal,
even if the parameter list is blank.
If a result list of a function type is blank, then it can be omitted from literal of the function type.
When a result list has most one result, then the result list doesn't need to be enclosed in a ()
if the literal of the result list contains no result names.
// The following three function types are identical.
func () (x int)
func () (int)
func () int
// The following two function types are identical.
func (a int, b string) ()
func (a int, b string)
...
to the element type of its (slice) type
in its declaration. Example:
func (values ...int64) (sum int64)
func (sep string, tokens ...string) string
A function type with variadic parameter can be called a variadic function type. A variadic function type and a non-variadic function type are absolutely not identical.
Some variadic functions examples will be shown in a below section.
It has been mentioned
several times in Go 101 that function types are incomparable types.
But like map and slice values, function values can compare with the untyped bare nil
identifier.
(Function values will be explained in the last section of the current article.)
As function types are incomparable types, they can't be used as the key types of map types.
A function prototype is composed of a function name and a function type (or signature).
Its literal is composed of the func
keyword,
a function name and the literal of a function signature literal.
func Double(n int) (result int)
In other words, a function prototype is a function declaration without the body portion. A function declaration is composed of a function prototype and a function body.
General function declarations and calls have been explained in function declarations and calls. Here introduces how to declare and call variadic functions.
// Sum and return the input numbers.
func Sum(values ...int64) (sum int64) {
// The type of values is []int64.
sum = 0
for _, v := range values {
sum += v
}
return
}
// An inefficient string concatenation function.
func Concat(sep string, tokens ...string) string {
// The type of tokens is []string.
r := ""
for i, t := range tokens {
if i != 0 {
r += sep
}
r += t
}
return r
}
From the above two variadic function declarations,
we can find that if a variadic parameter is declared with type portion as ...T
,
then the type of the parameter is []T
actually.
Print
, Println
and Printf
functions in the fmt
standard package are all variadic functions.
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)
The variadic parameter types of the three functions are all []interface{}
,
which element type interface{}
is an interface types.
Interface types and values will be explained interfaces in Go later.
[]T
:
[]T
,
and the slice must be followed by three dots ...
.
The passed slice is called as a variadic argument.
T
.
These arguments will be copied (or converted) as the elements of
a new allocated slice value of type []T
,
then the new allocated slice will be passed to the variadic parameter.
Note, the two manners can't be mixed in the same variadic function call.
An example program which uses some variadic function calls:package main
import "fmt"
func Sum(values ...int64) (sum int64) {
sum = 0
for _, v := range values {
sum += v
}
return
}
func main() {
a0 := Sum()
a1 := Sum(2)
a3 := Sum(2, 3, 5)
// The above three lines are equivalent to
// the following three respective lines.
b0 := Sum([]int64{}...) // <=> Sum(nil...)
b1 := Sum([]int64{2}...)
b3 := Sum([]int64{2, 3, 5}...)
fmt.Println(a0, a1, a3) // 0 2 10
fmt.Println(b0, b1, b3) // 0 2 10
}
Another example:
package main
import "fmt"
func Concat(sep string, tokens ...string) (r string) {
for i, t := range tokens {
if i != 0 {
r += sep
}
r += t
}
return
}
func main() {
tokens := []string{"Go", "C", "Rust"}
// manner 1
langsA := Concat(",", tokens...)
// manner 2
langsB := Concat(",", "Go", "C","Rust")
fmt.Println(langsA == langsB) // true
}
The following example doesn't compile, for the two variadic function call manners are mixed.
package main
// See above examples for the full declarations
// of the following two functions.
func Sum(values ...int64) (sum int64)
func Concat(sep string, tokens ...string) string
func main() {
// The following two lines both fail
// to compile, for the same error:
// too many arguments in call.
_ = Sum(2, []int64{3, 5}...)
_ = Concat(",", "Go", []string{"C", "Rust"}...)
}
init
and the same type func ()
.
_
, in which cases,
the declared functions can never be called.
Most function calls are evaluated at run time.
But calls to the functions of the unsafe
standard package
are always evaluated at compile time.
Calls to some other built-in functions,
such as len
and cap
,
may be evaluated at
either compile time or run time, depending on the passed arguments.
The results of the function calls evaluated at compile time can be
assigned to constants.
Let's repeat it again, like all value assignments in Go, all function arguments are passed by copy in Go. When a value is copied, only its direct part is copied (a.k.a., a shallow copy).
We can implement a function in Go assembly.
Go assembly source files are stored in *.a
files.
A function implemented in Go assembly is still needed to be declared in a
*.go
file, but the only the prototype of the function is needed
to be present. The body portion of the declaration of the function
must be omitted in the *.go
file.
return
terminating statement, there are some other kinds of terminating statements.
So a function body is not required to contain a return statement. For example,
func fa() int {
a:
goto a
}
func fb() bool {
for{}
}
The return results of a custom function call can be all discarded together.
However, the return results of calls to built-in functions, except recover
and copy
, can't be discarded,
though they can be ignored by assigning them to some blank identifiers.
Function calls whose results can't be discarded can't be used as deferred
function calls or goroutine calls.
A call to a function with single return result can always be used as a single value. For example, it can be nested in another function call as an argument, and can also be used as a single value to appear in any other expressions and statements.
If the return results of a call to a multi-result function are not discarded, then the call can only be used as a multi-value expression in two scenarios.package main
func HalfAndNegative(n int) (int, int) {
return n/2, -n
}
func AddSub(a, b int) (int, int) {
return a+b, a-b
}
func Dummy(values ...int) {}
func main() {
// These lines compile okay.
AddSub(HalfAndNegative(6))
AddSub(AddSub(AddSub(7, 5)))
AddSub(AddSub(HalfAndNegative(6)))
Dummy(HalfAndNegative(6))
_, _ = AddSub(7, 5)
// The following lines fail to compile.
/*
_, _, _ = 6, AddSub(7, 5)
Dummy(AddSub(7, 5), 9)
Dummy(AddSub(7, 5), HalfAndNegative(6))
*/
}
Note, for the standard Go compiler, some built-in functions break the universality of the just described rules above.
As mentioned above, function types are one kind of types in Go.
A value of a function type is called a function value.
The zero values of function types are represented with
the predeclared nil
.
When we declare a custom function, we also declared an immutable function value actually. The function value is identified by the function name. The type of the function value is represented as the literal by omitting the function name from the function prototype literal.
Note, built-in functions can't be used as values.
init
functions also can't be used as values.
Any function value can be invoked just like a declared function. It is fatal error to call a nil function to start a new goroutine. The fatal error is not recoverable and will make the whole program crash. For other situations, calls to nil function values will produce recoverable panics, including deferred function calls.
From the article value parts, we know that non-nil function values are multi-part values. After one function value is assigned to another, the two functions share the same underlying parts(s). In other words, the two functions represent the same internal function object. The effects of invoking two functions are the same.
An example:package main
import "fmt"
func Double(n int) int {
return n + n
}
func Apply(n int, f func(int) int) int {
return f(n) // the type of f is "func(int) int"
}
func main() {
fmt.Printf("%T\n", Double) // func(int) int
// Double = nil // error: Double is immutable.
var f func(n int) int // default value is nil.
f = Double
g := Apply // let compile deduce the type of g
fmt.Printf("%T\n", g) // func(int, func(int) int) int
fmt.Println(f(9)) // 18
fmt.Println(g(6, Double)) // 12
fmt.Println(Apply(6, f)) // 12
}
In the above example, g(6, Double)
and Apply(6, f)
are equivalent.
package main
import "fmt"
func main() {
// This function returns a function (a closure).
isMultipleOfX := func (x int) func(int) bool {
return func(n int) bool {
return n%x == 0
}
}
var isMultipleOf3 = isMultipleOfX(3)
var isMultipleOf5 = isMultipleOfX(5)
fmt.Println(isMultipleOf3(6)) // true
fmt.Println(isMultipleOf3(8)) // false
fmt.Println(isMultipleOf5(10)) // true
fmt.Println(isMultipleOf5(12)) // false
isMultipleOf15 := func(n int) bool {
return isMultipleOf3(n) && isMultipleOf5(n)
}
fmt.Println(isMultipleOf15(32)) // false
fmt.Println(isMultipleOf15(60)) // true
}
All functions in Go can be viewed as closures. This is why user experiences of all kinds of Go functions are so uniform and why Go functions are as flexible as those in dynamic languages.
The Go 101 프로젝트는 Github 에서 호스팅됩니다. 오타, 문법 오류, 부정확한 표현, 설명 결함, 코드 버그, 끊어진 링크와 같은 모든 종류의 실수에 대한 수정 사항을 제출하여 Go 101을 개선을 돕는 것은 언제나 환영합니다.
주기적으로 Go에 대한 깊이 있는 정보를 얻고 싶다면 Go 101의 공식 트위터 계정인 @go100and1을 팔로우하거나 Go 101 슬랙 채널에j가입해주세요.
reflect
표준 패키지sync
표준 패키지sync/atomic
표준 패키지