Go 1.22 Unveiled: Embrace Simplicity with New Slice Features and Enhanced Functions!

Go 1.22 Unveiled: Embrace Simplicity with New Slice Features and Enhanced Functions!

Starting from the new version Go 1.22, there have been some additions and changes to the behavior of slices, making them more developer-friendly.

The following provides an explanation and overview of the adjustments made to functions such as Concat, Delete, DeleteFunc, Replace, Compact, CompactFunc, Insert, etc.

# New Addition: Concat Function

In previous Go versions, when concatenating two slices, developers often had to manually write code like the following:

1
2
3
4
5
6
7
func main() {
s1 := []string{"A", "B", "C"}
s2 := []string{"Deep-A", "Water fish", "A"}

s3 := append(s1, s2...)
fmt.Println(s3)
}

Output:

1
[A B C Deep-A Water fish A]

If such concatenation is frequently used in a Go project, developers might create a utility function, similar to the following:

1
2
3
4
5
6
7
8
9
func concatSlice[T any](first []T, second []T) []T {
n := len(first)
return append(first[:n:n], second...)
}
func main() {
s1 := []string{"A", "Deep-A"}
s2 := []string{"Water fish", "C", "A"}
s3 := concatSlice(s1, s2) fmt.Println(s3)
}

Output:

1
[A Deep-A Water fish C A]

If there’s a need to merge more than two slices, the implementation of this function becomes more complex.

However!

Starting from Go 1.22, a new Concat function has been introduced, making it easier to concatenate (join) multiple slices without the need to maintain a custom method.

The signature of the Concat function is as follows:

1
func Concat[S ~[]E, E any](slices ...S) S

Usage example:

1
2
3
4
5
6
7
8
9
10
11
import (
"fmt"
"slices"
)
func main() {
s1 := []string{"A"}
s2 := []string{"Deep-A", "Blue fish", "B"}
s3 := []string{"Lucky fish", "A"}
resp := slices.Concat(s1, s2, s3)
fmt.Println(resp)
}

This function is implemented based on generics, eliminating the need to implement it internally for each data type. It provides a convenient way for users, but it’s essential to ensure that the input slice types are consistent.

The internal implementation of the function is relatively straightforward, as shown in the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Concat returns a new slice concatenating the passed in slices.
func Concat[S ~[]E, E any](slices ...S) S {
size := 0
for _, s := range slices {
size += len(s)
if size < 0 {
panic("len out of range")
}
}
newslice := Grow[S](nil, size)
for _, s := range slices {
newslice = append(newslice, s...)
}
return newslice
}

It’s worth noting that a panic is triggered when size < 0 , but this seems to be more of a defensive programming measure. In general scenarios, this should not be triggered.

# Changes in Behavior for Delete and Related Functions

Starting from Go 1.22, the behavior of functions related to slices that reduce the slice segment/size has been adjusted. After the slice has been reduced, elements between the new length and the old length will be set to zero values.

This adjustment affects functions like Delete, DeleteFunc, Replace, Compact, CompactFunc, etc.

Here are some specific examples, divided into the old version (Go 1.21) and the new version (Go 1.22 and later).

# Delete-related Functions

Old version:

1
2
3
4
5
6
func main() {
s1 := []int{11, 12, 13, 14}
s2 := slices.Delete(s1, 1, 3)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
}

Output:

1
2
s1: [11 14 13 14]
s2: [11 14]

In the new version, the program remains unchanged, but the output result has changed:

1
2
s1: [11 14 0 0]
s2: [11 14]

# Compact Function

Old version:

1
2
3
4
5
6
func main() {
s1 := []int{11, 12, 12, 12, 15}
s2 := slices.Compact(s1)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
}

Output:

1
2
s1: [11 12 15 12 15]
s2: [11 12 15]

In the new version, the program remains unchanged, but the output result has changed:

1
2
s1: [11 12 15 0 0]
s2: [11 12 15]

# Replace Function

Old version:

1
2
3
4
5
6
func main() {
s1 := []int{11, 12, 13, 14}
s2 := slices.Replace(s1, 1, 3, 99)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
}

Output:

1
2
s1: [11 99 14 14]
s2: [11 99 14]

In the new version, the program remains unchanged, but the output result has changed:

1
2
s1: [11 99 14 0]
s2: [11 99 14]

# Changes in Behavior for Insert Function, May Cause Panic

Old version:

1
2
3
4
5
6
func main() {
s1 := []string{"A", "Deep-A", "Water fish"}
s2 := slices.Insert(s1, 4)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
}

Output:

1
2
s1: [A Deep-A Water fish]
s2: [A Deep-A Water fish]

In the new version, the program remains unchanged, but the output result has changed:

1
2
3
4
5
panic: runtime error: slice bounds out of range [4:3]

goroutine 1 [running]:
slices.Insert[...](0xc00010e0c0, 0x10100000010, 0x7ecd5be280a8)
...

In the above scenario, when using the slices.Insert function and not filling in a specific element to insert, it would run normally in the old version but would cause a panic in the new version.

Of course, if an element is filled in from the beginning, it would cause a panic in both the new and old versions. It can be considered as fixing a boundary value issue.

March 2024 Programming Language Ranking, the gap between Python and other languages has never been so big!

March 2024 Programming Language Ranking, the gap between Python and other languages has never been so big!

# March 2024 Programming Language Ranking, the gap between Python and other languages has never been so big!

TIOBE’s March 2024 programming language rankings have been released, and the official title is: The gap between Python and the rest has never been that large.

The TIOBE index showed a relatively calm trend in February, but there were also some notable changes. According to TIOBE CEO Paul Jansen, the most noteworthy is that Python currently leads other language vendors by 4.5%, while Scratch returns to the top ten.

img

Meanwhile, Rust continues its upward trend.

img

Python, as a high-level programming language, has a wide range of applications and diverse advantages in today’s software development and data science fields.

Advantages of Python:

  • Easy to learn: Python syntax is concise and clear, similar to natural language, making it easy to learn and understand. This makes Python the preferred language for beginners, non-computer professionals, and education professionals.
  • Diverse Application Areas: Python can be used in various fields, including web development, data science, artificial intelligence, Machine Learning, scientific computing, natural language processing, network programming, etc. Its flexibility and versatility make Python a versatile programming language.
  • Python has a large and active community and ecosystem, supported by rich third-party libraries and tools such as NumPy, Pandas, TensorFlow, PyTorch, etc. These libraries can greatly simplify the development process and improve efficiency.
  • ** Cross-platform: ** Python is a cross-platform language that can run on a variety of operating systems, including Windows, Linux, macOS, etc., which makes it easy for developers to deploy their applications in different environments.
  • Rapid development: Python has the characteristics of rapid development and iteration. By using features such as dynamic typing and automatic memory management, prototypes can be quickly built and iterative development can be carried out.
  • ** Community support and rich documentation: ** Python has a large developer community with rich documentation, tutorials, and Q & A websites, such as Python official documentation, Stack Overflow, etc. Developers can easily obtain the help and resources they need.
  • ** Extensive tool support: ** Python not only supports a variety of Integrated Development Environments (IDEs), such as PyCharm, Jupyter Notebook, etc., but also supports a variety of text editors, such as Sublime Text, VS Code, etc. Developers can choose the appropriate tool according to their preferences for development.

In terms of application scope, Python is almost ubiquitous.

  • Web Development: Python excels in building web applications through frameworks such as Django and Flask.
  • Data science and artificial intelligence: Python has a wide range of applications in the fields of Data Analysis, data lake visualization, Machine Learning, and artificial intelligence. Libraries such as Pandas, NumPy, Scikit-learn, and Matplotlib support the development of these fields.
  • ** Scientific Computing: ** Python is also widely used in the fields of scientific computing and engineering. Libraries such as SciPy and SymPy provide rich scientific computing functions.
  • Python has strong capabilities in processing text data and natural language processing. Libraries such as NLTK and spaCy provide developers with rich tools and algorithms.

img

Python language version history:

img

Support time for each version:

img

Python official website: https://www.python.org/

Python Beginner Tutorial: https://www.runoob.com/python3/python3-tutorial.html

Long press the following QR code to access the Python beginner’s tutorial:

img

Python historical ranking change chart:

img

This month, the top ten rankings are:

# Python,C,C++,Java,C#,JavaScript,SQL,Go,Scratch,Visual Basic。

img

# Python, C, C++, Java, C #, JavaScript, SQL, Go, Scratch, Visual Basic historical change graph:

img

Ranked 11–20 programming languages, *PHP has fallen out of the top 10* :

img

Programming languages ranked 21–50:

img

51–100 programming languages:

ABAP, ABC, ActionScript, Algol, Apex, APL, Awk, bc, Bourne shell, Carbon, CFML, CHILL, CLIPS, Clojure, Crystal, Curl, DiBOL, Eiffel, Elixir, Groovy, Hack, Icon, IDL, Io, J, J#, JScript, LabVIEW, Ladder Logic, LiveCode, Modula-2, NATURAL, Nim, OCaml, OpenCL, OpenEdge ABL, PL/I, PL/SQL, PostScript, Q, Racket, REXX, Ring, Smalltalk, SPARK, SPSS, Tcl, VHDL, Wolfram, Zig

Historical rankings of major programming languages (1988–2024):

img

Programming Language “Celebrity List” (2003–2023):

img

TIOBE publishes a monthly ranking of programming languages based on the number of global technical engineers, courses, and third-party suppliers, including popular search engines and technical communities such as Google, Baidu, Wikipedia, Lingo, etc.

The data reflects the changing trend of mainstream programming languages, which can serve as a reference for our learning and work. However, each language has its own suitable application scenarios, so there is no need to pay too much attention to it. After all, the quality of a language depends on how it is used.

The following video shows the changing trends of programming languages in recent decades.

Reference: https://www.tiobe.com/tiobe-index/

Useful and Versatile Go Code Snippets

Useful and Versatile Go Code Snippets

During the development of production projects, I often find myself repeating code and unconsciously using certain techniques until I realize them later during a retrospective.

To address this issue, I developed a solution that has been very helpful for me, and I believe it might be useful for others as well.

Here are some useful and versatile code snippets randomly selected from my utility library, without specific categorization or system-specific techniques.

# 1. Timing Execution

If you want to track the execution time of a function in Go, there’s a simple and efficient technique with just one line of code, using the defer keyword. You only need a TrackTime function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Utility
func TrackTime(pre time.Time) time.Duration {
elapsed := time.Since(pre)
fmt.Println("elapsed:", elapsed)

return elapsed
}

func TestTrackTime(t *testing.T) {
defer TrackTime(time.Now()) // <--- THIS

time.Sleep(500 * time.Millisecond)
}

// Output:
// elapsed: 501.11125ms

# 1.5. Two-Stage Deferred Execution

Go’s defer is not only for cleanup tasks but can also be used for preparation tasks. Consider the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func setupTeardown() func() {
fmt.Println("Run initialization")
return func() {
fmt.Println("Run cleanup")
}
}

func main() {
defer setupTeardown()() // <--------
fmt.Println("Main function called")
}

// Output:
// Run initialization
// Main function called
// Run cleanup

The beauty of this pattern is that with just one line of code, you can accomplish tasks like:

  • Opening a database connection and then closing it.
  • Setting up a mock environment and then tearing it down.
  • Acquiring a distributed lock and then releasing it.

“Well, that seems clever, but how practical is it in real-world scenarios?”

Remember the timing execution technique? We can do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
func TrackTime() func() {
pre := time.Now()
return func() {
elapsed := time.Since(pre)
fmt.Println("elapsed:", elapsed)
}
}

func main() {
defer TrackTime()()

time.Sleep(500 * time.Millisecond)
}

Note! What if there’s an error when I connect to the database?

Indeed, patterns like defer TrackTime() or defer ConnectDB() might not handle errors properly. This technique is most suitable for testing or when you are willing to take the risk of fatal errors, as shown in the following test-oriented approach:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func TestSomething(t *testing.T) {
defer handleDBConnection(t)()
// ...
}

func handleDBConnection(t *testing.T) func() {
conn, err := connectDB()
if err != nil {
t.Fatal(err)
}

return func() {
fmt.Println("Closing connection", conn)
}
}

This way, database connection errors can be handled during testing.

# 2. Pre-allocate Slices

According to insights from the article “Performance Improvements in Go,” pre-allocating slices or maps can significantly improve the performance of a Go program. However, it’s worth noting that if we are not careful and use append instead of indexing (like a[i] ), this approach may sometimes lead to errors. Did you know that we can use a pre-allocated slice without specifying the array length (zero) as explained in the mentioned article? This allows us to use the pre-allocated slice just like using append :

1
2
3
4
5
6
7
// Instead of
a := make([]int, 10)
a[0] = 1

// Use it like this
b := make([]int, 0, 10)
b = append(b, 1)

# 3. Method Chaining

Method chaining technique can be applied to function (pointer) receivers. To illustrate this, let’s consider a Person struct with two functions AddAge and Rename for modifications:

1
2
3
4
5
6
7
8
9
10
11
12
type Person struct {
Name string
Age int
}

func (p *Person) AddAge() {
p.Age++
}

func (p *Person) Rename(name string) {
p.Name = name
}

If you want to add age to a person and then rename them, the conventional way is:

1
2
3
4
5
6
func main() {
p := Person{Name: "Aiden", Age: 30}

p.AddAge()
p.Rename("Aiden 2")
}

Alternatively, we can modify the AddAge and Rename functions’ receivers to return the modified object itself, even though they typically don’t return anything:

1
2
3
4
5
6
7
8
9
func (p *Person) AddAge() *Person {
p.Age++
return p
}

func (p *Person) Rename(name string) *Person {
p.Name = name
return p
}

By returning the modified object itself, we can easily chain multiple function receivers together without adding unnecessary lines of code:

1
p = p.AddAge().Rename("Aiden 2")

# 4. Go 1.20 Allows Slicing into an Array or Array Pointer

When we need to convert a slice to a fixed-size array, direct assignment is not possible, for example:

1
2
3
4
a := []int{0, 1, 2, 3, 4, 5}
var b [3]int = a[0:3]

// Cannot assign a[0:3] (type []int) to a variable of type [3]int (incompatible assignment)

With the introduction of this feature in Go 1.17, and with the release of Go 1.20, the conversion process becomes simpler with more convenient literals:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Go 1.20
func Test(t *testing.T) {
a := []int{0, 1, 2, 3, 4, 5}
b := [3]int(a[0:3])

fmt.Println(b) // [0 1 2]
}

// Go 1.17
func TestM2e(t *testing.T) {
a := []int{0, 1, 2, 3, 4, 5}
b := *(*

[3]int)(a[0:3])

fmt.Println(b) // [0 1 2]
}

Just a quick reminder: you can use a[:3] instead of a[0:3] . I mention this for clarity.

# 5. Package Initialization with “import _”

Sometimes, in a library, you might come across import statements combining an underscore (_) like this:

1
2
3
import (
_ "google.golang.org/genproto/googleapis/api/annotations"
)

This executes the package’s initialization code (init function) without needing to create a named reference to it. This allows you to initialize packages, register connections, and perform other tasks before running the code.

Let’s better understand how it works with an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Underscore
package underscore

func init() {
fmt.Println("init called from underscore package")
}
// Main
package main

import (
"fmt"
_ "lab/underscore"
)

func main() {}
// Output: init called from underscore package

# 6. Importing with “import .”

After understanding how to import with an underscore, let’s see how more commonly used is the dot (.) operator.

As a developer, the dot (.) operator can be used to import exported identifiers from an imported package without specifying the package name. This is a useful shortcut for lazy developers, especially when dealing with long package names in a project, like externalmodel or doingsomethinglonglib .

To demonstrate, here’s a simple example:

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
. "math"
)

func main() {
fmt.Println(Pi) // 3.141592653589793
fmt.Println(Sin(Pi/2)) // 1
}

# 7. Go 1.20 Allows Merging Multiple Errors into a Single Error

Go 1.20 introduces new features to the errors package, including support for multiple errors and changes to errors.Is and errors.As . One of the new functions added in errors is Join , which we’ll discuss in detail below:

1
2
3
4
5
6
7
8
9
10
11
12
var (
err1 = errors.New("Error 1st")
err2 = errors.New("Error 2nd")
)

func main() {
err := err1
err = errors.Join(err, err2)

fmt.Println(errors.Is(err, err1)) // true
fmt.Println(errors.Is(err, err2)) // true
}

If there are multiple tasks causing errors, you can use the Join function instead of manually managing arrays. This simplifies the error handling process.

# 8. Checking if an Interface Is Truly Nil

Even if an interface holds a value of nil , it doesn’t mean the interface itself is nil . This can lead to unexpected errors in a Go program. Therefore, it’s important to know how to check if an interface is truly nil .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
var x interface{}
var y *int = nil
x = y

if x != nil {
fmt.Println("x != nil") // <-- Actual output
} else {
fmt.Println("x == nil")
}

fmt.Println(x)
}

// Output:
// x != nil
// <nil>

How do we determine if the interface{} value is nil ? Fortunately, there’s a simple tool to help us with that:

1
2
3
4
5
6
7
func IsNil(x interface{}) bool {
if x == nil {
return true
}

return reflect.ValueOf(x).IsNil()
}

# 9. Parsing time.Duration in JSON

When parsing JSON, using time.Duration can be a cumbersome process because it requires adding 9 zeros (i.e., 1000000000) after the second. To simplify this process, I created a new type called Duration :

1
type Duration time.Duration

To parse strings (like “1s” or “20h5m”) into an int64 type duration, I also implemented custom parsing logic for this new type:

1
2
3
4
5
6
7
8
9
10
11
12
func (d *Duration) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
dur, err := time.ParseDuration(s)
if err != nil {
return err
}
*d = Duration(dur)
return nil
}

However, it’s worth noting that the variable ‘d’ should not be nil, or it might result in marshaling errors. Alternatively, you can also check ‘d’ at the beginning of the function.