Syntactic Sugars that You Should Know in Golang

Exploring Syntactic Sugars in Go: A Comprehensive Guide

Walking with a friend in the dark is better than walking alone in the light.
— Helen Keller

中文文章: https://programmerscareer.com/zh-cn/golang-syntactic-sugar/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: If you choose to repost or use this article, please cite the original source.

Introduction to Syntax Sugars in Go

Before I get into the specifics of syntactic sugar, let me explain to you what syntactic sugar is.

In computer science, “syntactic sugar” refers to a programming syntax that is internally transformed into a base syntax in order to make a program easier to read or express. In other words, this syntax does not introduce new features, but rather provides a more convenient way of programming.

Golang, being a modern language, contains a lot of syntactic sugar to ease the programmer’s burden and make the code more readable.

For example, many high-level programming languages have the ++ operator, which is syntactic sugar used to increment the value of a variable by 1. Thus, instead of i = i + 1, we can type i++, which is shorter and faster to type, and which expresses the same incremental operation.

Next we’ll go through the common syntactic sugars in Golang, detailing each one. This will help you understand and use Golang better and write more compact and readable code.

Of course, the goal is not only to learn these features, but also to understand when and why to use them. A responsible Go developer knows not only how to utilize these syntactic sugars, but also when it is appropriate to use them.

Variadic Parameters

Basic Introduction

Go allows a function to take any number of values as arguments, and Go provides the … operator to be used only at the end of a function’s parameter list. When using this operator, you should be aware of the following points:

  • A function can have at most one variadic parameter;
  • The type of a variadic parameter is always a slice type;
  • The last parameter of a function can be a variadic parameter.

Declaration and Calling

The declaration of a variadic function is similar to that of a regular function, except that the last parameter must be a variadic parameter. In the function body, a variadic parameter is treated as a slice.

1
2
3
4
5
6
7
8
func SumData(values …int64) (sum int64) {
// Type of values is []int64.
sum = 0
for _, v := range values {
sum += v
}
return
}

When calling a variadic function, you can use two styles to pass arguments to a variadic parameter of type []T:

1. Pass a slice as an argument. This slice must be assignable to a value of type []T (or can be implicitly converted to type []T). Following this argument, there must be three dots ‘…’.
2. Pass zero or more arguments that can be implicitly converted to type T (or can be assigned to a value of type T). These arguments will be added to an anonymous slice of type []T that is created at runtime, and then this slice will be passed as an argument to the function call.

Note that you cannot mix these two styles of argument passing in the same variadic function call.

Note that you cannot mix these two styles of argument passing in the same variadic function call.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
a0 := SumData()
a1 := SumData(3)
a3 := SumData(3, 4, 8)
// The top three lines are equivalent to the bottom three lines.
b0 := SumData([]int64{})…
b1 := SumData([]int64{2})…
b3 := SumData([]int64{2, 3, 5})…
fmt.Println(a0, a1, a3)
fmt.Println(b0, b1, b3)
}
// print
// 0 3 15
// 0 3 15

The PrintPrintln and Printf functions in the fmt standard library package are all variadic functions. Their declarations are roughly as follows:

1
2
3
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)

Ignoring Unnecessary Information

We want to initialize the init function in a package but do not want to use any of the methods in the package. In this case, we can use the _ operator to rename the import of an unused package:

1
import _ "github.com/tfrain"

Sometimes we don’t necessarily use the return values of a function, and we have to come up with a creative name for it. Is there a way to handle unnecessary return values? Of course, we can use the _ operator to assign the unwanted values to a blank identifier, which allows us to ignore them:

1
_, ok := test(a, b int)
  1. Sometimes we want to exclude certain fields from serialization in JSON. The - operator can help us with this. Go structures provide a labeling feature, and in the structure tag, we can use the - operator to perform special handling on fields that we don’t want to serialize:
1
2
3
4
5
type Person struct {
Name string `json:"-"`
Age string `json:"age"`
Email string `json:"email,omitempty"`
}
  1. When we use json.Marshal to serialize a structure, it does not ignore empty values by default. Instead, it outputs the zero value of the field’s type (the zero value of a string type is “”, and the zero value of an object type is nil). If we want to ignore empty fields during serialization, we can add the omitempty attribute to the structure tag:
1
2
3
4
5
6
type Person struct {
Name string `json:"name"`
Age string `json:"age"`
Email string `json:"email,omitempty"`
Active bool `json:"active,omitempty"`
}

Declaration Statements

Short Variable Declaration

In some other programming languages, it’s not common to declare variables every time they’re used. In Go, you can declare and initialize local variables using the syntax name := expression instead of using the var statement for declaration. This can reduce the number of steps required for declaration:

1
2
3
var a int = 10
same as
a := 10

When using short variable declaration, there are two things to note:

  • Short variable declaration can only be used inside functions, not for initializing global variables.
  • Short variable declaration introduces a new variable, so you can’t declare the same variable again in the same scope.
  • When declaring multiple variables using short variable declaration, if one variable is new, you can use short variable declaration, but if all variables are already declared, you can’t declare them again.

Declaring Variables with Unspecified Length

In Go, arrays usually have a fixed length, and you have to specify the length when declaring the array. However, you can also omit the length and use the operator to declare arrays. In this case, you just need to fill in the element values, and the compiler will handle the length:

1
a := […]int{1, 3, 5} // same as a := [3]int{1, 3, 5}

When declaring a large array, you can use the operator to set specific values for some indices:

1
a := […]int{1: 20, 999: 10} // array length is 1000, index 1 has value 20, index 999 has value 10, other indices have value 0

Checking logic

Checking if a Key Exists in a Map in Go

Go provides the syntax value, ok := m[key] to check if a key exists in a map. This syntax is commonly used to only check the ok value. If the key exists, it returns the value associated with the key; otherwise, it returns an empty value:

1
2
3
4
5
6
7
8
9
10
import "fmt"

func main() {
dict := map[string]int{"tfrain": 1}
if value, ok := dict["tfrain"]; ok {
fmt.Println(value)
} else {
fmt.Println("Key:tfrain not exist")
}
}

Type Assertions

We often use interface in Go, where there are two types: interfaces with methods and empty interfaces. Since Go 1.18 does not have generics, we can use empty interfaces as a pseudo-generic type. When we use empty interfaces as input parameters or output values, we need to use type assertions to obtain the type we need. In Go, the syntax for type assertions is as follows:

1
value, ok := x.(T)

Here, x is an interface type, and T is a specific type. This syntax requires distinguishing the type of x. If x is an empty interface type:

The type assertion for empty interface types is essentially a comparison of _type and the type to be matched in eface. If the comparison is successful, the value is assembled in memory and returned. If the comparison fails, the register is cleared, and the default value is returned.

If x is a non-empty interface type:

The type assertion for non-empty interface types is essentially a comparison of *itab in iface. If the comparison is successful, the value is assembled in memory and returned. If the comparison fails, the register is cleared, and the default value is returned.

Common(20+) Software Interview Questions(+ Answers) about MySQL/Redis/Kafka Kafka interviews: How does Kafka send messages reliably?

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×