đŁ Go Funcs â Baby-Gopherâs Visual Guide
Easily understand Go funcs with visuals.

Note: This guide is only about introducing Go functions, not about: variadic, defer, and external funcs; or methods, http, and marshaling, etc.
What is a function?
A function is a separate and reusable block of code which can be run again and again. Functions may accept input values and they may return output values.

Why do we need functions?
- Increasing the readability, testability, and maintainability
- Making some part of code separately executable
- Composing things from smaller things
- Adding behavior to types
- Organizing the code
- To be DRY


Input Parameters and Result Types
Input params are used to pass values to funcs. Result types are used to return data from funcs. The returned values from a func named as the âresult valuesâ.

Takes an int type âinput paramâ named as âsâ and returns a âresult typeâ as int type without a name.


A signature is a funcâs type â consisting of the types of its input params and its result types.

func jump()
// signature: func()func Len(s string) int
// signature: func(string) intfunc multiply(n ...float64) []float64
// signature: func(...float64) []float64

Funcs in Go are first-class values which can be passed around.
flen := Len
flen("Hello!")



When a func is called, its body will run with the provided input params. The func will return one or more result values if at least one result type was declared.

You can directly return from RuneCountInString func because it returns an int as well.
func Len(s string) int {
return utf8.RuneCountInString(s)
}lettersLen := Len("Hey!")

This func uses an expression next to a return.
func returnWithExpression(a, b int) int {
return a * b * 2 * anotherFunc(a, b)
}

Funcâs Body
Every bracket group creates a new block and any declared identifiers are only visible inside that block.
const message = "Hello world đ"func HelloWorld() {
name := "Dennis"
message := "Hello, earthling!"
}

HelloWorld()/*â message constant is visible here.
â name variable inside the func is not visible here.
â shadowed message variable inside the func is not visible here.*/


Now, letâs see the different styles of the input params and the result types declarations.


Declares an input param named âsâ with a type of âStringâ and an integer result type.


A funcâs input params and result types act as variables.




Automatic type-assignment
Types are automatically declared for the previous params.


These declarations are the same:
func scale(width, height, scale int) (int, int)func scale(width int, height int, scale int) (int, int)

Error value
Some funcs conventionally return errors â multiple result values makes this convenient to use.
func write(w io.Writer, str string) (int, error) {
return w.Write([]byte(s))
}write(os.Stdout, "hello")// Output: hello

Directly returning from the Write func is the same as returning multiple result types. Because, it also returns an int and an error value.
func write(w io.Writer, str string) (int, error) {
n, err := w.Write([]byte(s))
return n, err
}

If everything is fine you can just return nil as a result:
func div(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("divide by zero")
} return a / b, nil
}r, err := div(-1, 0)// err: divide by zero


Discarding the result values
You can use the blank-identifier to discard the result values.
/*
Suppose that we have a func like this:
*/func TempDir(dir, prefix string) (name string, err error)

Discarding the error value (2nd result value):
name, _ := TempDir("", "test")

Discarding all the result values:
TempDir("", "test")

Omitting the param names
You can also use the blank-identifier in the unused input params as a name â to satisfy an interface, for an example (or this).
func Write(_ []byte) (n int, err error) {
return 0, nil
}


Named result params let you use the result values as variables and they enable you to use a naked return.

Pos result value acts like as a variable and the biggest func returns it with a naked return (without any expressions next to it).
// biggest returns the biggest number's index in nums
func biggest(nums []int) (pos int) { if len(nums) == 0 {
return -1
} m := nums[0] for i, n := range nums {
if n > m {
m = n
pos = i
}
} // returns the pos
return
}pos := biggest([]int{4,5,1})// Output: 1


When to use named result params?
- Named result params are mostly used as a hint for the result values.
- Do not use them just to skip declaring variables inside a func.
- Use them if they make your code more readable.

There is also a controversial optimization trick when you use the named result values, however, the compiler probably will be fixed soon to negate this.

Be careful about the shadowing problem
func incr(snum string) (rnum string, err error) {
var i int // start of a new scope
if i, err := strconv.Atoi(snum); err == nil {
i = i + 1
}
// end of the new scope rnum = strconv.Itoa(i)
return
}incr("abc") // Output: 0 and nil

The variables, i and err are only visible inside the if block. In the end, the err should not have been ânilâ, because, âabcâ couldnât be converted to an integer and there was an error, but we missed it.


Pass-by-value
Pass func sets the input paramsâ values to their zero-values:
func pass(s string, n int) {
s, n = "", 0
}

We pass two variables to pass func:
str, num := "knuth", 2
pass(str, num)

After the func ends, our variables are still the same.
str is "knuth"
num is 2

This is because, when we pass params to a func, theyâre copied automatically as new variables. This is called pass-by-value.


Pass-by-value and the pointers
Below func accepts a pointer to a string variable. It changes the value the ps pointer points to. Then it will try to set the passed pointerâs value to nil. So, the pointer will not point to the passed string variableâs address anymore.
func pass(ps *string) {
*ps = "donald"
ps = nil
}

We define a new variable s and then we take its address by using the ampersand operator and store its address inside a new pointer variable: ps.
s := "knuth"
ps := &s

Letâs pass ps to the pass func.
pass(ps)
After the func ends, we see that the value inside the s variable has changed. But, the ps pointer still points to a valid address of the s variable.
// Output:
// s : "donald"
// ps: 0x1040c130

The ps pointer is passed-by-value to the pass func, only the address it points to get copied to a new pointer variable inside the pass func. So, setting it to nil inside the func had no effect on the passed pointerâs value.


Letâs run the code to understand it better.

This completes the parameter declaration styles of a func.
Now, letâs see how to name the funcs and the input params and the result types properly.

Naming funcs
Some of the goals of using a func are to increase the readability and the maintainability of the code. You may need to bend these suggestions depending on the problem.

Be a Minimalist
When choosing names be a minimalist. Prefer short, descriptive and meaningful names.
// Not this:
func CheckProtocolIsFileTransferProtocol(protocolData io.Reader) bool// This:
func Detect(in io.Reader) Name {
return FTP
}// Not this:
func CreateFromIncomingJSONBytes(incomingBytesSource []byte)// This:
func NewFromJSON(src []byte)

Name in MixedCaps
// This:
func runServer()
func RunServer()// Not this:
func run_server()
func RUN_SERVER()
func RunSERVER()

Acronyms should be all uppercase:
// Not this:
func ServeHttp()// This:
func ServeHTTP()

Choose descriptive param names
// Not this:
func encrypt(i1, a3, b2 byte) byte// This:
func encrypt(privKey, pubKey, salt byte) byte// Not this:
func Write(writableStream io.Writer, bytesToBeWritten []byte)// This:
func Write(w io.Writer, s []byte)
// Types make it clear, no need for the names

Use verbs
// Not this:
func mongo(h string) error// This:
func connectMongo(host string) error// If it's in Mongo package, just:
func connect(host string) error

Use is / are
// Not this:
func pop(new bool) item// This:
func pop(isNew bool) item

Omit types in the name
// Not this:
func show(errorString string)// This:
func show(err string)

Getters and Setters
There are no getters and setters in Go. However, you can imitate them by using funcs.
// Not this:
func GetName() string// This:
func Name() string// Not this:
func Name() string// This:
func SetName(name string)

Features Go funcs donât support:
So, you can stop Duckling or Googling for these things. There are some workarounds for some of them which Iâll write about them in the upcoming posts.
- Func overloading â It can be imitated with type assertions.
- Pattern matcher funcs.
- Default parameter values in the declaration.
- Specifying input params by name in the declaration in any order.

Alright, thatâs all for now. Thank you for reading so far.
Letâs stay in touch:
- đ© Join my newsletter
- đŠ Follow me on twitter
- đŠ Get my Go repository for free tutorials, examples, and exercises
- đș Learn Go with my Go Bootcamp Course
- â€ïž Do you want to help? Please clap and share the article. Let other people also learn from this article.