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.

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.

What happened when Fortran entered the top 10 programming language rankings in May 2024?

What happened when Fortran entered the top 10 programming language rankings in May 2024?

TIOBE’s programming language rankings for May 2024 have been released. The official title is: Fortran in the top 10, what is going on?

Fortran, a veteran language in the programming world, has once again entered the Top 10 list of programming languages after more than 20 years of silence.

This makes people curious, what made this “old guy” radiate a second spring?

img

Since its birth in 1957, Fortran has never stopped innovating itself. The latest ISO Fortran 2023 standard is proof of its continuous progress.

Fortran has won a place in the field of numerical and mathematical computing with its excellent performance and mature technology, beating many competitors.

The free and open source feature of Fortran gives it an advantage in cost-effectiveness.

img

Official website address: https://fortran-lang.org/

On Amazon, the number of search results for “Fortran programming” far exceeds that of emerging languages Kotlin and Rust, reflecting the market’s continued interest and demand for Fortran.

Comparative analysis with other languages:

  • Although Python is widely popular, it lacks speed.
  • MATLAB is user-friendly, but expensive.
  • C/C++ fast, but lacks native support for mathematical calculations.
  • R is similar to Python, but slightly less popular and faster.
  • Julia, as a rising star, has great potential, but her maturity still needs time to be verified.
  • Fortran is fast, supports native mathematical calculations, has mature technology, and is completely free. These features have enabled Fortran to steadily advance in the jungle of programming languages.

The revival of Fortran is an indisputable fact. It proves that in the continuous evolution of technology, classics and innovation can coexist, and veterans can also radiate new vitality.

img

Fortran, short for “Formula Translation”, is a high-level programming language originally designed for numerical and scientific computing.

  • ** Origin: ** Fortran language was developed by a team led by John W. Backus of IBM in 1957 and is one of the earliest high-level programming languages.
  • The original design goal of Fortran was to help scientists and engineers write programs for scientific computing more easily.
  • ** Syntax Features: ** Fortran has a concise syntax and direct support for mathematical expressions, which makes it very popular in the fields of science and engineering.
  • ** Compiler: ** Fortran programs are usually translated into machine code through a compiler for execution on a computer.
  • Fortran has undergone multiple standardization and upgrades, including Fortran IV, Fortran 66, Fortran 77, Fortran 90, Fortran 95, Fortran 2003, Fortran 2008, and the latest version of Fortran 2018.
  • Parallel computing: Fortran 90 and later versions have added support for parallel computing, maintaining its importance in the high-performance computing (HPC) field.
  • Fortran has a wide range of applications in scientific computing, engineering simulation, numerical analysis, weather forecasting, quantum physics simulation and other fields.
  • ** Cross-platform: ** Fortran compiler supports a wide range of operating systems, including Windows, Linux and macOS.

This month, the top ten rankings are:

# Python,C,C++,Java,C#,JavaScript,Visual Basic,Go,SQL,Fortran

img

# Python,C,C++,Java,C#,JavaScript,Visual Basic,Go,SQL, Fortran historical change curve:

img

Ranked 11-20 programming languages, PHP fell to 16, up one place from last month :

img

Programming languages ranked 21-50:

img

51-100 programming languages:

ABC, ActionScript, Algol, Apex, APL, bc, Boo, Carbon, CIL, CL (OS/400), CLIPS, Clojure, Common Lisp, Curl, DiBOL, Erlang, Factor, Groovy, Hack, Icon, Inform, Io, J, JScript, Ladder Logic, Lingo, LiveCode, LPC, MQL5, NATURAL, Nim, OCaml, OpenEdge ABL, Oxygene, Paradox, PL/I, PowerShell, Pure Data, Q, Ring, RPG, Scheme, Smalltalk, SPARK, Standard ML, WebAssembly, Wolfram, X++, Xojo, XPL

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 the past 20 years.

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

 Rust writes the new generation web framework Teo, which supports both Node and Python, with amazing speed!

Rust writes the new generation web framework Teo, which supports both Node and Python, with amazing speed!

Today I will share the topic. With the rapid development of web technology, development has become increasingly complex and requires more time and effort. Today, I will introduce this new generation web framework written in Rust.

Web project development is becoming increasingly complex, which also brings many challenges to developers. It is necessary to flexibly use the latest web development frameworks to improve development efficiency and respond to constantly changing needs.

Recently, while browsing Github, I came across a new generation of web framework with structure as its core - TEO, a web framework suitable for Rust, Node.js, and Python , which can greatly improve the work efficiency of application developers when using web servers and databases. It is worth mentioning that this project has now become GVP project of Gitee.

img

What is TEO?

Teo is a new generation web framework. It consists of the following parts:

  • Teo schema: an intuitive and innovative schema language that is descriptive and readable
  • Teo Server: High-performance core written in Rust and API bindings for Node.js, Python, and Rust
  • Teo CLI: A CLI tool for reading schemas and performing tasks, including database migration, running servers, and seeding data
  • Teo Query Client: Automatically generated type-safe query builder for multiple platforms and languages

Why use TEO?

Traditional web frameworks

  • Many boilerplate codes : developers need to write duplicate code for each route, which is not only time-consuming but also error-prone.
  • Lack of built-in features : Traditional frameworks often do not provide common functions such as filtering, sorting, and pagination, and developers need to implement these functions themselves.
  • Debugging Difficulty : Debugging SQL queries and MongoDB aggregation is complex, error-prone, and time-consuming.
  • Type and interface duplication : In front-end development, developers need to re-declare the data types and interfaces of the back-end model, resulting in duplication of work.

Teo Modern Web Framework:

  • Reduce boilerplate code : The new generation of frameworks reduces the amount of boilerplate code developers need to write by providing more abstraction and automation tools.
  • Built-in common features : Modern frameworks often have common features such as filtering, sorting, and pagination built in, making it easy for developers to implement these features without having to write them from scratch.
  • The new generation framework provides better tools and interfaces to simplify the debugging process of database queries and aggregation, reducing the possibility of errors.
  • Unified data types and interfaces : Modern frameworks allow developers to define data models and interfaces on the server side, and then automatically generate Client code, reducing duplication of effort and improving consistency between front-end and back-end code.

More information about Teo can be found on the official website.

Functions and features

Let’s take a look at what features Teo provides.

  • Innovative structural definition inspired by GraphQL and Prisma
  • Automatic database migration
  • Supports Rust, Node.js, and Python
  • Supports MySQL, PostgreSQL, SQLite, and MongoDB.
  • Generated ORM types and interfaces
  • Query Client generated for frontend
  • Very efficient and high performance
  • Data cleaning, transformation, and validation
  • Built-in user sessions
  • Built-in permission check
  • First in, last out Middleware
  • Custom routes and handlers

img

Quick Start

A typical Teo workflow consists of the following parts:

1. Select your language stack

Teo supports three server-side programming languages: Node.js, Python, and Rust. You can choose a language you are familiar with to work with.

  • Node.js works well with web technologies.
  • Python is great for interacting with artificial intelligence infrastructures.
  • Aimed for the Highest Performance, Rust is hard to write.

Teo cares about code repetition and productivity. Therefore, the front-end Client is generated by Teo. Teo supports 5 front-end languages: TypeScript, Swift, Kotlin, C #, and Dart. This almost covers mainstream front-end technologies. Easily use these generated Clients or share them with front-end developers.

Note: Currently, Swift, Kotlin, C #, and Dart are not yet supported, but the Teo team has set a goal for 2024, and I believe it should be supported soon.

2. Structure your data

Teo has an innovative and easy-to-read schema language inspired by GraphQL and Prisma. Database configuration, server configuration, models and enumerations, and routing handlers are all declared in it. What you write is what you think, what you see is what you get, which is really great.

Teo adopts the pattern you described in detail and performs database migration, input validation, and transformation for you.

3. ORM entity and server code

Automatically generated routing handlers can meet 80% of business needs. There are always some custom requirements that require developers to write code to handle. Teo is a mature web framework, not a no-code or low-code tool. Let Teo do the heavy lifting and generate ORM entities. Use Teo’s programming API to write code in any server language supported by Teo.

4. Generate Client for Front-end

Issues with traditional front-end development:

  • Developers repeatedly write interface request and parameter processing code in multiple front-end projects, which is inefficient and error-prone.

Teo framework solution:

  • Teo automatically generates type-safe front-end code, reducing repetitive work and improving development efficiency.
  • Supports multiple programming languages to adapt to different development needs.
  • Integrate into existing projects or create new packages to provide flexibility.

Interested partners can go and experience it now!

Reference materials:

Rust and Go in 2024

Rust and Go in 2024

Which is better, Rust or Go? Which language should you choose for your next project and why? How do they compare in terms of performance, simplicity, security, features, scalability, and concurrency? What do they have in common and what are their fundamental differences? Let’s find the answer through a friendly and fair comparison of Rust and Go.

# 1. Rust and Go are both great

Firstly, it is very important to note that Go and Rust are both absolutely excellent programming languages. They are modern, powerful, widely adopted, and provide excellent performance.

Rust is a low-level statically typed multi-paradigm programming language that focuses on safety and performance - Gints Dreimanis [2]

However:

Go is an open-source programming language that makes it easy to build simple, reliable, and efficient software golang.org [3]

In this article, I will try to briefly outline which scenarios I think Go is the ideal choice and which scenarios Rust may be a better choice.

# 2. Similarities

What is the common goal of the two languages?

# 2.1 Memory safety

Historically, one of the biggest causes of software errors and security bugs is unsafe or incorrect access to memory.

Rust and Go handle this problem in different ways, but both aim to be smarter and safer in managing memory than other languages.

# 2.2 Fast and compact executable files

They are all compile languages, which means your program will be directly compiled into executable machine code so that you can deploy the program as a single binary file. Compared with interpreted languages such as Python or Ruby, this also makes Rust and Go programs have extremely fast execution speed.

# 2.3 Common language

Rust and Go are both powerful and extensible general-purpose programming languages that you can use to develop various modern software. Both have excellent standard libraries and thriving third-party ecosystems, as well as strong commercial support and a large user base.

# 2.4 Pragmatic programming style

Although Go and Rust both have functional features related to functional and Object Oriented Programming (OOP), they are both pragmatic languages designed to solve problems in the most appropriate way.

# 2.5 Suitable for large-scale development

Rust and Go both have some useful features that make them suitable for large-scale programming, whether it is for large teams, large codebases, or both.

For example, both Rust and Go use standard code formatting tools (Go’s gofmt, Rust’s rustfmt), which puts an end to useless arguments about the placement of parentheses.

Both also have excellent built-in high-performance standard build and dependency management tools; no more struggling with complex third-party build systems or learning a new one every few years.

# 3. Differences

Although Rust and Go share many similarities, in some areas, rational people may prefer one language over the other to meet specific project needs.

# 3.1 Performance

Go and Rust are both very fast. However, Go’s design is more conducive to fast compilation, while Rust is optimized for fast execution.

Rust’s runtime performance is also more consistent because it does not use garbage collection mechanism. On the other hand, Go’s garbage collector reduces some of the burden on programmers, making it easier to focus on solving the main problems rather than memory management details.

For areas where execution speed outweighs all other considerations (such as game programming, operating system kernels, web browser components, and real-time control systems), Rust is a better choice.

# 3.2 Simple

From a design perspective, Go is a small language with very few syntax, keywords, and language structures. You can quickly learn the basics of Go and use the language to improve work efficiency.

This gives Go an advantage in projects with short time spans or teams that need to quickly introduce a large number of new programmers, especially when they are relatively inexperienced.

# 3.3 Functional features

On the other hand, Rust has almost all the features of programming languages you can imagine, as well as some features that you may not be able to imagine. This makes it a powerful and expressive language that can accomplish the same thing in multiple different ways.

If you’re transitioning to Rust from another language, you can probably find Rust equivalents for most of the features you’re used to. This gives Rust an advantage when large projects need to migrate from legacy languages like C++ or Java.

# 3.4 Concurrency

Unlike most languages, Go is designed with built-in support for concurrent programming, such as goroutines (a lightweight version of threads) and channels (a safe and efficient way to communicate data between concurrent tasks).

These make Go the perfect choice for large-scale concurrent applications such as network servers and microservices.

# 3.5 Security

Rust is carefully designed to ensure that programmers cannot do unsafe things they do not want to do, such as overwriting shared variables. The compiler requires you to specify the way data is shared between different parts of the program and can detect many common errors and bugs.

Therefore, the so-called “battle with borrow checkers” is a common complaint among new Rust programmers. Implementing programs with safe Rust code often means fundamentally rethinking their design, which can be frustrating, but when reliability is your top priority, the benefits of doing so are worth it.

# 3.6 Scale

Go aims to make it easy for you to expand projects and development teams. Its minimalist design brings a certain consistency, and the existence of a clearly defined standard style means that any Go programmer can read and understand new code libraries relatively quickly.

When it comes to large-scale software development, clarity is better than intelligence. For large organizations, especially many distributed teams, Go is a good choice. Its fast build time is also conducive to rapid testing and deployment.

# Step 4 Trade-offs

The design teams of Rust and Go made some very different choices, so let’s take a look at some areas where these trade-offs make these two languages very different from each other.

# 4.1 Garbage collection

Generally speaking, languages with garbage collection and automatic memory management functions (such as Go) can quickly and easily develop reliable and efficient programs, which is the most important for some people.

However, garbage collection, due to its performance overhead and stop-the-world pauses, can make program behavior unpredictable at runtime, and some people find this inconsistency unacceptable.

Programmers must clarify that the language responsible for allocating and releasing each byte of memory (such as Rust) is more suitable for real-time or ultra-high-performance applications.

# 4.2 Abstract

The history of computer programming is an increasingly complex and abstract story that allows programmers to solve problems without worrying too much about how the underlying machines actually work.

This makes the program easier to write and possibly more portable. However, for many programs, accessing hardware and precisely controlling the execution of the program is more important.

Rust’s goal is to make programmers “closer to metal” and have more control, while Go abstracts architectural details and brings programmers closer to problems.

# 4.3 Speed

Rust has made many design trade-offs to achieve the best execution speed. In contrast, Go cares more about simplicity and is willing to sacrifice some (runtime) performance for it.

At this point, whether you prefer Rust or Go depends on whether you are willing to spend more time waiting for the program to build or wait for the program to run.

# 4.4 Correctness

Go and Rust both aim to help you write correct programs, but in different ways: for example, Go provides an excellent built-in unit test framework and a rich standard library, while Rust focuses on eliminating runtime errors using its borrow checker mechanism.

To be fair, it is easier to write a given program in Go, but the result may contain errors more easily than the Rust version. Rust imposes disciplinary constraints on programmers, but Go lets programmers choose the level of discipline they want to apply to specific projects.

# 5. Conclusion

I hope this article can make you believe that both Rust and Go are worth considering seriously. You should reject the dilemma of this mistake: you can only learn one of them. In fact, the more languages you know, the higher your value as a software developer.

Every new language you learn will give you a new way of thinking, which can only be a good thing. The most important factor for the quality and success of any software project is not the choice of language, but the skills of the programmer.

When using the language that suits you best, you become most proficient and can enjoy the most programming pleasure. Therefore, if the question is “Should I learn Rust or Go?”, the only correct answer is “Yes”.

References

[1] 《Rust vs Go in 2024》: https://bitfieldconsulting.com/golang/rust-vs-go

[2] Gints Dreimanis: https://serokell.io/blog/rust-guide

[3] golang.org: https://go.dev/