Functions
Functions are reusable code blocks that you can call whenever you wish.
Format:
func func_name (input_params) (result_params) {
// your code here
}
Every package-level function has a name, but avoid using Go-preserved names like 'init' and 'main'.
Functions can accept zero input parameters.
Ex:
func greeting() {
fmt.Println("Hello")
}
Functions can accept numerous input arguments.
Ex:
func greetingWithNames(name1 string, name2 string) {
fmt.Printf("Hello %s and %s\n", name1, name2)
}
func greetingWithNamesV2(name1, name2 string) {
fmt.Printf("Hello %s and %s\n", name1, name2)
}
The "return" statement makes a function to stop and end instantly.
Ex:
func determineOddOrEven(number int) {
if number%2 == 0 {
fmt.Printf("%d is Even\n", number)
return
}
fmt.Printf("%d is Odd\n", number)
}
A function may not return any value.
Ex:
func greeting() {
fmt.Println("Hello")
}
A function may return a value without the need for parenthesis.
Ex:
func multiply(a int, b int) int {
return a * b
}
A function may return multiple result values.
Ex:
func divide(a int, b int) (int, error) {
if b > 0 {
return a / b, nil
}
return 0, errors.New("Cannot be divided by 0")
}
If we need to return err, according to Go convention, the last parameter should be an error result value.
Go does not support function overloading. Refer to this article.
Do not print the error in the custom function. It is preferable to return the error to the caller and allow him to make a decision.
Input params and result values are copied
The values are copied regardless of whether they are supplied from the function caller or returned by the function to the caller.
As a result, any changes to the values within the functions will have no effect on the function callers' values.
Ex:
func main() {
a, b := 2, 3
c := add(a, b)
fmt.Printf("main: a: %d, b: %d, c: %d\n", a, b, c)
}
func add(a int, b int) int {
a += b
fmt.Printf("func: a: %d, b: %d\n", a, b)
return a
}
Result:
func: a: 5, b: 3
main: a: 2, b: 3, c: 5
Try to avoid changing package level variables
If you declare variables at the package level, any file in the same package can modify them.
It is not best practice since bugs are easy to introduce and hard to detect.
Ex:
main.go
package main
import "fmt"
var Counter int
func main() {
fmt.Println("Hello")
showCounterValue()
increaseCounter()
showCounterValue()
}
another_file.go
package main
import "fmt"
func increaseCounter() {
Counter++
}
func showCounterValue() {
fmt.Printf("Counter: %d \n", Counter)
}
The better way is to confine variables to a function.
Ex:
package main
import "fmt"
func main() {
counter := 0
fmt.Println("Hello")
showCounterValue(counter)
counter = increaseCounter(counter)
showCounterValue(counter)
}
func increaseCounter(counter int) int {
counter++
return counter
}
func showCounterValue(counter int) {
fmt.Printf("Counter: %d \n", counter)
}
* 'counter' is declared inside main function. Therefore, only main function can acess it.
* The function 'increaseCounter' and 'showCounterValue' can only get the value of 'counter' variable because their input parameters are copied.
* Use 'Errorf() to create and return an error value from the given format and arguments.
Naked Return
Go's return values may be named. If so, they are treated as variables defined at the top of the function.
A return statement without arguments returns the named return values. This is known as a "naked" return.
However, it is best to avoid using it because it reduces readability. Refer to this article.
Ex:
func divide(a int, b int) (int, error) {
var (
result int
err error
)
if b == 0 {
err = fmt.Errorf("%d cannot be divided by %d", a, b)
return result, err
}
result = a / b
return result, err
}
func divideWithNamedReturn(a int, b int) (result int, err error) {
if b == 0 {
err = fmt.Errorf("%d cannot be divided by %d", a, b)
return
}
result = a / b
return
}
Passing different type of data
In Go, every value is copied when assigned to a variable or passed to a function.
MAP
In Go, the map value corresponds to the memory address of the "Map header".
So, even if it is copied, the values of two distinct variables will point to the same memory address.
As a result, changes to the passed map will have an effect on the caller's map.
Ex:
func main() {
// Set up text mapping for English and Chinese.
dict := map[string]string{
"Hi": "嗨",
"Welcome": "歡迎",
"Sorry": "抱歉",
}
fmt.Printf("disc: %v\n", dict)
updateDict(dict)
fmt.Printf("disc: %v\n", dict)
}
func updateDict(dict map[string]string) {
dict["Yo"] = "喲"
}
Result:
disc: map[Hi:嗨 Sorry:抱歉 Welcome:歡迎]
disc: map[Hi:嗨 Sorry:抱歉 Welcome:歡迎 Yo:喲]
Slice
In Go, the slice value point to the the "Slice Header" which track 'length', 'capacity' and the memory of the backing array.
As a result, any modifications to the passed slice will only affect the "Slice Header".
And nothing will change the caller's slice.
Ex:
func main() {
names := []string{
"Frank", "Jake",
}
fmt.Printf("slice: %v\n", names)
appendSice(names)
fmt.Printf("slice: %v\n", names)
}
func appendSice(names []string) {
names = append(names, "Mary")
}
Result:
slice: [Frank Jake]
slice: [Frank Jake]
To synchronize the changes, we must first return the updated slice and then update the caller's slice.
Ex:
func main() {
names := []string{
"Frank", "Jake",
}
fmt.Printf("slice: %v\n", names)
names = appendSice(names)
fmt.Printf("slice: %v\n", names)
}
func appendSice(names []string) []string {
names = append(names, "Mary")
return names
}
Result:
slice: [Frank Jake]
slice: [Frank Jake Mary]
Array
When we assign one array to another, Go creates a new memory location for the new array.
As a result, the old and new arrays are not linked, and any modifications to the passed array will only affect the caller's array.