From Unknown to Expert: Golang Statements You Need to Know

Rarely known but useful statements in Golang
Mar 29 2023 · 7 min read

Introduction

In Go, statements are used to instruct the program to perform a specific task or action. They are written as a sequence of tokens and are executed by the Go interpreter or compiler.

Statements in Go can be classified into various categories such as,

  • Declaration statements — use to declare variables, constants, and types.
  • Control flow statements — use to control the flow of execution.
  • Function statements — use to define and invoke functions.
  • Advanced statements — more complex statements that deal with advanced Go features.

Understanding the different types of statements and their usage is essential for effective Go programming. By using the appropriate statements for each task, programmers can write more efficient and effective code.

Today, we will see some rarely known but useful statements in Golang with real examples.

Note: I had skipped some basic statements like declaration statements(var, const, type), control flow statements(if…else, for, switch), as they are simple and easy to understand.

Let’s explore Golang's statements one by one.


Sponsored

Positive thinking leads to positive outcomes. Try out Justly, build good habits, and start thinking positively today!


Control flow statements

1. Panic and Recover

The panic statement is used to stop the normal execution of a program with error .

The recover function is used to handle the panic and continues the execution. It returns the value that was passed to the panic function, which can be used to return an appropriate error response to the client.

For instance, we are building a web server in Go, and we want to ensure that the server does not crash in case of an unexpected error. We can use panic and recover to recover from such errors and return a meaningful error response to the client.

Example:

func handleRequest(w http.ResponseWriter, r *http.Request) {
    defer func() {
        if r := recover(); r != nil {
            log.Println("Recovered error: ", r)
            http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        }
    }()
    // code that may cause a panic, will be handled by recover()
    panic('Request not handled properly') 
}

// output will be printed
Recovered error: Request not handled properly

By using panic and recover in this way, we can ensure that our web server remains stable and responds gracefully to unexpected errors, rather than crashing or returning meaningless error messages to the client.

2. Goto statements

This allows you to jump to a labeled statement within the same function.

Here’s an example of how you can use the goto statement:

func main() {
    i := 0
loop:
    if i < 10 {
        fmt.Printf("%d ", i)
        i++
        goto loop
    }
}

//output
0 1 2 3 4 5 6 7 8 9 

While the goto statement can be useful in certain situations, it is generally not recommended, because it can make the code harder to understand and maintain. It can also lead to complex and convoluted control flow, which can make debugging more difficult.

It’s worth noting that the use of goto is limited to the same function and can only be used to jump to a labeled statement within that function. We can’t use a goto statement to jump to a statement in a different function.

3. Break and Continue

These statements are used to alter the normal flow of execution in loops.

The break is used to exit a loop prematurely inside a for, switch, or select statement. It immediately terminates the innermost loop and control resumes at the next statement following the loop.

for i := 0; i < 10; i++ {
    if i == 5 {
        break
    }
    fmt.Printf("%d ", i)
}

//output
0 1 2 3 4 

continue is used to skip the current iteration of a loop and move on to the next iteration. When continue is encountered, the current iteration is immediately terminated and the loop continues with the next iteration.

for i := 0; i < 10; i++ {
    if i%2 == 0 {
        continue
    }
    fmt.Printf("%d ", i)
}

//output
1 3 5 7 9

Functional statements

1. Anonymous function

An anonymous function is a function without a name. It can be defined inline and used immediately, or assigned to a variable and used later.

// execute when used
sum := func(a, b int) int {
    return a + b
}

// will execute immediately
func() {
    fmt.Println("Hello, anonymous function!") 
}()

fmt.Println("Sum is : ", sum(3, 4))


// output
Hello, anonymous function!
Sum is : 7

2. Function closures

A function closure is created when a function returns an inner function that references a variable defined in the outer function’s scope.

Let’s consider an example where we have a function that returns another function that adds a value to a running total:

func adder() func(int) int {
    total := 0  // outer function's variable
    return func(x int) int {  // can modify total's value across function calls
        total += x  // references total and modify it
        return total
    }
}


// use this function as below
a := adder()
fmt.Println(a(1)) // 0 + 1 = 1
fmt.Println(a(2)) // 1 + 2 = 3
fmt.Println(a(3)) // 3 + 3 = 6

Function closures can be used for a wide range of applications, from managing state in concurrent code to creating memoized functions.

3. Blank identifier

We all are using _(underscore), when we want to ignore the returned values of functions. This is known as a blank identifier in Go.

_, err := someFunction()
if err != nil {
    fmt.Println("Error:", err)
}

4. Variadic functions

Variadic functions are functions that can take a dynamic number of arguments. This is useful when you don’t know how many arguments you will need to pass to a function. It will take ... as an argument.

Here is an example of concatenating an arbitrary number of strings using a variadic function.

func concatStrings(separator string, words ...string) string {
    var result string
    for i, s := range words {
        if i > 0 {
            result += separator
        }
        result += s
    }
    return result
}

// Here's an example of how we can use this function in a Go program:
result := concatStrings(" ", "hello", "world", "!")
fmt.Println(result) // Output: "hello world !"

By using a variadic function like this, we can provide a flexible and convenient way for users without having to worry about the details of how the function works.


Other statements

1. Type assertions

Type assertions are used to convert an interface value to a concrete type. This is useful when you have an interface value and you want to use it as a specific type.

Suppose we are working on a command-line tool that takes user input and performs some operation based on that input. We want to be able to handle a wide range of input types, including integers, floats, and strings. To do this, we can use a variadic function that accepts a slice of interface{} types, which can hold any value.

Here’s an example of this:

func performOperation(args ...interface{}) {
    for _, arg := range args {
        switch val := arg.(type) {
        case int:
            // Handle integer input
            fmt.Println("Integer input:", val)
        case float64:
            // Handle floating-point input
            fmt.Println("Float input:", val)
        case string:
            // Handle string input
            fmt.Println("String input:", val)
        default:
            // Handle unrecognized input
            fmt.Println("Unrecognized input:", val)
        }
    }
}

// use function as below
performOperation(77, "hey", 0.54)

By using type assertions in this way, we can handle a wide range of input types in a flexible and extensible way, without having to define separate functions or code paths for each type.

2. Defer statements

This can be used to defer the execution of a function until the surrounding function has been completed.

Here’s an example of using the defer statement to ensure that a file is closed after it's opened,

func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close() // defer the file.Close() call until the function returns
   
    // read file contents
    return nil
}

By deferring the file.Close() call until after the function returns, we ensure that the file is always properly closed, even in the case of an error. This can help avoid resource leaks and other problems associated with improperly closing files.

This statement is commonly used in Go for tasks like closing files, releasing locks, and releasing other resources like database instances that need to be cleaned up after a function has been completed.

3. Label statements

This can be used to define a label for a certain block of code. Labels are most commonly used with the break,continue and goto statements to control the flow of execution in nested loops or switch statements.

outer:
for i := 0; i < 5; i++ {
    for j := 0; j < 5; j++ {
        if i*j >= 10 {
            break outer
        }
        fmt.Println(i, j)
    }
}

If we had used a break statement without the label, it would have only broken out of the inner loop, and the outer loop would have continued to execute.

It’s worth noting that labels can only be used within the same function as the labeled statement. You cannot use a label to jump to a statement in a different function.

4. Select statement

This is used to wait on multiple channels for input. It blocks until one of the channels has data ready to be processed.

This can be used in many different situations, such as waiting for data from multiple sources or coordinating multiple goroutines in concurrent programming.

select {
    case <- channel1:
        // handle channel1 case
    case data := <- channel2:
        // handle channel2 case
    case channel3 <- value:
        // handle channel3 case
    default:
        // handle default case
}

In this example, we have a select statement that waits for data to be sent or received on one of three channels. The default case is used to handle situations where none of the other cases are ready. It blocks until one of the communication operations is ready. This means that the statement will not exit until one of the cases is executed.


Conclusion

We have explored several rarely known statements that can be incredibly useful in certain situations with real-world examples.

As a Golang developer, these statements can greatly enhance your programming skills and make you a more efficient developer.

That’s it for today. Always aim for the top 😎.


Similar articles


sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.


sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.

contact-footer
Say Hello!
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.