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 | func SumData(values …int64) (sum int64) { |
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 | func main() { |
The Print
、Println
and Printf
functions in the fmt
standard library package are all variadic functions. Their declarations are roughly as follows:
1 | func Print(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) |
- 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 | type Person struct { |
- 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 astring
type is “”, and the zero value of an object type isnil
). If we want to ignore empty fields during serialization, we can add theomitempty
attribute to the structure tag:
1 | type Person struct { |
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 | var a int = 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 | import "fmt" |
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.
Comments