Preface
Hello, everyone, I am asong.
Each language has its own syntactic sugar. For example, java syntactic sugar has methods to lengthen parameters, unboxing and packing, enumeration, for-each, etc. Go is no exception. It also has its own syntactic sugar. Mastering these syntactic sugars can help us improve development efficiency. Therefore, this article will introduce some syntactic sugars in Go. The summary may not be complete. Welcome to add it in the comment section.
Variable length parameters
Go language allows a function to take any number of values as parameters. Go language has built-in... operators, and the... operator can only be used at the last formal parameter of the function. You must pay attention to the following things when using it:
- The variable length parameter must be at the last of the function list;
- When variable length parameters are parsed as slices, it is
- The variable length parameter must be the same type
Since our function can receive variable length parameters, we can also pass slices when passing parameters... to unpack and convert them into parameter lists. The append method is the best example:
var sl []int sl = append(sl, 1) sl = append(sl, sl...)
The append method is defined as follows:
// slice = append(slice, elem1, elem2) // slice = append(slice, anotherSlice...) func append(slice []Type, elems ...Type) []Type
Declare an indefinite length array
Arrays have fixed lengths. When declaring an array, we must declare the length, because the array must confirm its length when compiling. However, sometimes for me who want to be lazy, I just don’t want to write the length of the array. Is there any way for him to calculate it by himself? Of course, when declaring an array using the... operator, you just fill in the element value and leave the others to the compiler itself;
a := [...]int{1, 3, 5} // The array length is3,Equivalent to a := [3]{1, 3, 5}
Sometimes we want to declare a large array, but some indexes want to set special values can also be done using the... operator:
a := [...]int{1: 20, 999: 10} // The array length is100, Subscript1The element value is20,Subscript999The element value is10,The other element values are0
init function
Go language provides init functions executed before the main function. After initializing each package, the init function will be automatically executed. There can be multiple init functions in each package, and there can also be multiple init functions in the source file in each package. The loading order is as follows:
Starting from the current package, if the current package contains multiple dependent packages, the dependent package is initialized first, and each package is initialized recursively initialized layer by layer. In each package, it is executed from front to back according to the dictionary order of the source file. In each source file, constants and variables are initialized first, and finally the init function is initialized. When multiple init functions appear, it is executed from front to back in sequence. After each package is loaded, it is returned recursively, and finally the current package is initialized!
The init function is implemented. No matter how many times the package is imported, the init function will only be executed once. Therefore, using init can be applied to service registration, middleware initialization, singleton pattern implementation, etc. For example, the pprof tool we often use, it uses the init function and routes in the init function:
//go/1.15.7/libexec/src/cmd/trace/ func init() { ("/io", serveSVGProfile(pprofByGoroutine(computePprofIO))) ("/block", serveSVGProfile(pprofByGoroutine(computePprofBlock))) ("/syscall", serveSVGProfile(pprofByGoroutine(computePprofSyscall))) ("/sched", serveSVGProfile(pprofByGoroutine(computePprofSched))) ("/regionio", serveSVGProfile(pprofByRegion(computePprofIO))) ("/regionblock", serveSVGProfile(pprofByRegion(computePprofBlock))) ("/regionsyscall", serveSVGProfile(pprofByRegion(computePprofSyscall))) ("/regionsched", serveSVGProfile(pprofByRegion(computePprofSched))) }
Ignore the guide packet
Go language has a clean code obsession with designers, and avoids code abuse as much as possible in design, so Go language's guide package must be used. If the guide package is not used, it will cause compilation errors. However, in some scenarios, we will encounter situations where we only want to import packages but not use them. For example, the init function mentioned above, we only want to initialize the init function in the package, but will not use any methods in the package. At this time, we can use the _ operator symbol to rename and import an unused package:
import _ "/asong"
Ignore fields
In our daily development, we usually pile up shit on shit, and we will reuse it directly when we encounter a method that can be used. However, we may not use all the return values of this method. We also rack our brains to think of a name for them. Is there any way to not deal with the return values you don’t need to deal with? Of course, there is still the _ operator, assigning unnecessary values to the empty identifier:
_, ok := test(a, b int)
json serialization ignores a field
In most business scenarios, we will serialize structs, but sometimes we want some fields in json to not participate in serialization. The - operator can help us handle it. The Go language structure provides label function. Use the - operator in the structure label to perform special processing on fields that do not need to be serialized, and use it as follows:
type Person struct{ name string `json:"-"` age string `json: "age"` }
json serialization ignores empty value fields
When we use for serialization, we will not ignore the null value in struct. The default output field type zero value (the zero value of string type is "", and the zero value of object type is nil...). If we want to ignore these fields without values during serialization, we can add the omitempty tag to the structure tag:
type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Age int `json: "age"` } func test() { u1 := User{ Name: "asong", } b, err := (u1) if err != nil { (" failed, err:%v\n", err) return } ("str:%s\n", b) }
Running results:
str:{"name":"asong","Age":0}
We did not add the Omitempty tag in the Age field. The result of the serialization of the json is empty, and the email field is ignored;
Short variable declaration
Every time I use a variable, I have to declare the function first. For a lazy person like me, I really don’t want to write it because I am used to writing python. So can I use it directly without declaring the variable in Go? We can use the syntax form of name := expression to declare and initialize local variables. Compared with using var declarations, we can reduce the declaration steps:
var a int = 10 // etc.a := 10
- There are two comments when declaring short variables:
- Short variable declarations can only be used within functions and cannot be used to initialize global variables.
- Short variable declaration means introducing a new variable, and variables cannot be declared repeatedly in the same scope.
If one of the variables in a multivariate declaration is a new variable, then a short variable declaration can be used, otherwise the variable cannot be declared repeatedly;
Type Assertion
We usually use interface, one is an interface with method, and the other is an empty interface. There were no generics before Go1.18, so we can use empty interface{} as a pseudogeneric. When we use empty interface{} as an entry parameter or return value, we will use type assertions to get the type we need. The syntax format of type assertions in Go language is as follows:
value, ok := x.(T) or value := x.(T)
x is the interface type, T is a specific type, the first is a safe assertion, and the second is the failure of the assertion will trigger panic; here the type assertion needs to distinguish the type of x, if x is an empty interface type:
The essence of the empty interface type assertion is to compare the _type in eface with the type to be matched. The return value is assembled in memory successfully when the match fails, and the register is directly cleared and the default value is returned.
If x is a non-empty interface type:
The essence of the non-empty interface type assertion is a comparison of ¨*itab in ifeface. *itab The match will be successfully assembled in memory. If the matching fails, the register will be cleared and the default value will be returned.
Slice loop
Slices/arrays are operations we often use. In Go, for range syntax is provided to quickly iterate objects. Arrays, slices, strings, maps, channels, etc. can all be traversed. In summary, there are three ways:
// Method 1: Only traverse and do not care about data, suitable for slices, arrays, strings, maps, channelsfor range T {} // Method 2: traversal to get the index or array, slice, array and string are index, map is key, channel is datafor key := range T{} // Method 3: traversal to obtain index and data, which is suitable for slices, arrays, and strings. The first parameter is the index, the second parameter is the corresponding element value, the first parameter is the key, and the second parameter is the corresponding value;for key, value := range T{}
Determine whether the map key exists
Go language provides syntax value, ok := m[key] to determine whether the key in map exists. If it exists, it will return the value corresponding to the key. If it does not exist, it will return the null value:
import "fmt" func main() { dict := map[string]int{"asong": 1} if value, ok := dict["asong"]; ok { (value) } else { ("key:asong does not exist") } }
Select control structure
Go language provides the select keyword. Select and channel can make Goroutine wait for multiple channels to read or write at the same time. Before the channel state changes, select will block the current thread or Goroutine. Let’s take a look at an example:
func fibonacci(ch chan int, done chan struct{}) { x, y := 0, 1 for { select { case ch <- x: x, y = y, x+y case <-done: ("over") return } } } func main() { ch := make(chan int) done := make(chan struct{}) go func() { for i := 0; i < 10; i++ { (<-ch) } done <- struct{}{} }() fibonacci(ch, done) }
Select has a similar control structure to switch. Unlike switch, the expression in case in select must be a channel sending and receiving operation. When two cases in select are triggered at the same time, one of them will be executed randomly. Why is it executed randomly? Random introduction is to avoid the occurrence of hunger problems. If we execute them in sequence every time, if both cases always meet the conditions, then the subsequent cases will never be executed.
The use of select in the example above is a blocking transceiver operation until a channel changes state. We can also use the default statement in select, so the select statement will encounter these two situations when executing:
- When there is a Channel that can be sent and received, the corresponding case of the Channel is directly processed;
- When there is no Channel that can be sent and received, execute the statement in default;
Note: The operations on nil channel will be blocked all the time. If there is no default case, only the nil channel's select will be blocked all the time.
The above is the detailed content of the grammatical sugar skills that improve development efficiency in Go. For more information about grammatical sugar in Go, please pay attention to my other related articles!