From 3147074661e98d46f0938b8b0c5ab901b35a8693 Mon Sep 17 00:00:00 2001
From: Go101 <22589241+go101@users.noreply.github.com>
Date: Mon, 18 Mar 2024 02:15:49 +0800
Subject: [PATCH 1/4] update generics 100 to 1.22
---
pages/generics/222-about-this-book.md | 3 +++
pages/generics/333-about-go-generics.md | 2 +-
.../555-type-constraints-and-parameters.md | 14 +++++-----
...antiations-and-type-argument-inferences.md | 6 ++---
...tions-on-values-of-type-parameter-types.md | 26 +++++++++----------
...88-the-status-quo-of-Go-custom-generics.md | 18 ++++++-------
6 files changed, 36 insertions(+), 33 deletions(-)
diff --git a/pages/generics/222-about-this-book.md b/pages/generics/222-about-this-book.md
index 935fa8ff..49afabcf 100644
--- a/pages/generics/222-about-this-book.md
+++ b/pages/generics/222-about-this-book.md
@@ -29,6 +29,9 @@ the behavior differences between Go toolchain versions.
The same argument valuation moment rules also apply to goroutine function calls.
The following program will output 123 789
.
diff --git a/pages/fundamentals/control-flows.html b/pages/fundamentals/control-flows.html
index f80718f4..885dbfba 100644
--- a/pages/fundamentals/control-flows.html
+++ b/pages/fundamentals/control-flows.html
@@ -25,16 +25,17 @@
for-range
loop block for
- container types.
+ for-range
loop block to iterate integers,
+ all kinds of containers,
+ and channels.
type-switch
multi-way conditional execution block for
- interface types.
+ interfaces.
select-case
block for
- channel types.
+ channels.
for
and for-range
loop blocks are called
loop control flow blocks.
-We can use continue
statements to end a loop step in advance
+We can use continue
statements to end a loop iteration in advance
in a loop control flow block, i.e. continue to the next iteration of the loop.
-Only the basic control flow code blocks and code jump statements will be explained
-in the current article, other ones will be explained in
-many other Go 101 articles later.
+The current article mainly explains the basic control flow code blocks
+and code jump statements. Go 1.22 introduced for range anInteger {...}
+loops will be also touched. Other control flow code blocks will be explained
+in many other coming Go 101 articles.
for
Loop Control Flow Blocks
-The Condition
expression will be evaluated at each loop step.
+The Condition
expression will be evaluated at each loop iteration.
If the evaluation result is false
, then the loop will end.
Otherwise the body (a.k.a., the explicit code block) of the loop will get executed.
-The PostSimpleStatement
will be executed at the end of each loop step.
+The PostSimpleStatement
will be executed at the end of each loop iteration.
for
Loop Control Flow Blocks-If the
InitSimpleStatement
in a for
block is a short variable declaration statement,
-then the declared variables will be viewed as being declared in the top nesting implicit code block of the for
block.
+If the InitSimpleStatement
in a for
block
+is a short variable declaration statement,
+then the declared loop variables will be viewed as being declared
+in the top nesting implicit code block of the for
block.
For example, the following code snippet prints 012
instead of 0
.
for i := 0; i < 3; i++ {
@@ -317,10 +321,33 @@ for
Loop Control Flow Blocks
-A break
statement can be used to make execution jump out of the
+
+Note: Go 1.22 modified the semantics of for
loop blocks.
+
+-
+Prior to Go 1.22, every declared loop variable used in a
for
loop block
+was shared by all iterations during executing the loop block.
+
+-
+Since Go 1.22, every declared loop variable used in a
for
loop
+will be instantiated as a distinctive instance at the start of each iteration.
+
+
+
+
+For most cases, the semantic change doesn't change code behavior.
+But sometimes, it does.
+So the semantic change breaks backward compatibility.
+Since Go 1.22, every
+Go source file should be specified a Go version to
+reduce the damage as small as possible.
+
+
+A break
statement can be used to make execution jump out of a
for
loop control flow block in advance,
if the for
loop control flow block is the innermost breakable
control flow block containing the break
statement.
+For example, the following code also prints 0
to 9
.
i := 0
for {
@@ -335,7 +362,7 @@ for
Loop Control Flow Blocks
-A continue
statement can be used to end the current loop step
+A continue
statement can be used to end the current loop iteration
in advance (PostSimpleStatement
will still get executed),
if the for
loop control flow block is the innermost
loop control flow block containing the continue
statement.
@@ -351,6 +378,67 @@ for
Loop Control Flow Blocks
+
+Use for-range
Control Flow Blocks to Iterate Integers
+
+
+
+
+for-range
loop blocks can be used to iterate integers,
+all kinds of containers,
+and channels.
+The current article only explains how to use for-range
loop blocks
+to iterate integers.
+
+
+
+Note: using for-range
loop blocks to iterate integers is only supported since Go 1.22.
+
+
+The following code
+
+for i = range anInteger {
+ ...
+}
+
+
+is actually a short form of
+
+for i = 0; i < anInteger; i++ {
+ ...
+}
+
+
+
+
+Similarly,
+
+for i := range anInteger {
+ ...
+}
+
+
+is actually a short form of
+
+for i := 0; i < anInteger; i++ {
+ ...
+}
+
+
+
+
+For example, the last example in the last section is equivalent to
+
+for i := range 10 {
+ if i % 2 == 0 {
+ continue
+ }
+ fmt.Print(i)
+}
+
+
+
+
switch-case
Control Flow Blocks
@@ -499,7 +587,7 @@ switch-case
Control Flow Blocks
For example, the standard Go compiler thinks the case 6, 7, 8
line
in the above example is invalid if that line is not commented out.
But other compilers may think that line is okay.
-In fact, the current standard Go compiler (version 1.21.n)
+In fact, the current standard Go compiler (version 1.22.n)
allows duplicate boolean case expressions,
and gccgo (v8.2) allows both duplicate boolean and string case expressions.
@@ -818,7 +906,7 @@ break
and continue
Statements With Labels
the label must be declared just before a loop control flow block
which contains the continue
statement.
We can view the label name as the name of the loop control flow block.
-The continue
statement will end the current loop step of
+The continue
statement will end the current loop iteration of
the loop control flow block in advance, even if the loop control flow block
is not the innermost loop control flow block containing
the continue
statement.
diff --git a/pages/fundamentals/defer-more.html b/pages/fundamentals/defer-more.html
index 7deac839..d4a0b333 100644
--- a/pages/fundamentals/defer-more.html
+++ b/pages/fundamentals/defer-more.html
@@ -16,7 +16,7 @@ Calls to Many Built-in Functions With Return Results Can't Be Deferred
In Go, the result values of a call to custom functions can be all absent (discarded).
However, for built-in functions with non-blank return result lists,
the result values of their calls mustn't be absent
-(up to Go 1.21),
+(up to Go 1.22),
except the calls to the built-in copy
and recover
functions.
On the other hand, we have learned that the result values of a deferred function call must be discarded,
so the calls to many built-in functions can't be deferred.
diff --git a/pages/fundamentals/details.html b/pages/fundamentals/details.html
index e17c9e52..c1ce6aeb 100644
--- a/pages/fundamentals/details.html
+++ b/pages/fundamentals/details.html
@@ -771,7 +771,7 @@ Addresses of different zero-sized values may be equal, or not.
fmt.Println(&x, &y, &o, &p)
- // For the standard Go compiler (1.21.n),
+ // For the standard Go compiler (1.22.n),
// x, y, o and p escape to heap, but
// a, b, m and n are allocated on stack.
@@ -786,7 +786,7 @@ Addresses of different zero-sized values may be equal, or not.
-The outputs indicated in the above code are for the standard Go compiler 1.21.n.
+The outputs indicated in the above code are for the standard Go compiler 1.22.n.
@@ -1230,16 +1230,16 @@ The capacity of the result slice of a conversion from a string to byte/rune
In the following example, if the last fmt.Println
line is removed,
-the outputs of the two lines before it print the same value 32
,
-otherwise, one print 32
and one print 8
-(for the standard Go compiler 1.21.n).
+the outputs of the two lines before it print the same value 5
;
+otherwise, one print 5
and one print 8
+(for the standard Go compiler 1.22.n).
package main
import "fmt"
func main() {
- s := "a"
+ s := "abcde"
x := []byte(s) // len(s) == 1
fmt.Println(cap([]byte(s))) // 32
fmt.Println(cap(x)) // 8
diff --git a/pages/fundamentals/go-toolchain.html b/pages/fundamentals/go-toolchain.html
index 8724bace..f4bd6add 100644
--- a/pages/fundamentals/go-toolchain.html
+++ b/pages/fundamentals/go-toolchain.html
@@ -29,8 +29,8 @@ Install Go Toolchain
The version of a Go Toolchain release is consistent with
the highest Go language version the release supports.
-For example, Go Toolchain 1.21.x supports
-all Go language versions from 1.0 to Go 1.21.
+For example, Go Toolchain 1.22.x supports
+all Go language versions from 1.0 to Go 1.22.
diff --git a/pages/fundamentals/interface.html b/pages/fundamentals/interface.html
index 0aaf6078..af076154 100644
--- a/pages/fundamentals/interface.html
+++ b/pages/fundamentals/interface.html
@@ -34,7 +34,7 @@
Interface Types and Type Sets
The requirements defined for an interface type are expressed
by embedding some interface elements in the interface type.
-Currently (Go 1.21), there are two kinds of interface elements, method elements and type elements.
+Currently (Go 1.22), there are two kinds of interface elements, method elements and type elements.
-
@@ -200,7 +200,7 @@
Basic Interface Types
-Currently (Go 1.21), every basic interface type could be defined entirely
+Currently (Go 1.22), every basic interface type could be defined entirely
by a method set (may be empty).
In other words, a basic interface type doesn't need type elements to be defined.
@@ -330,7 +330,7 @@ Value Boxing
-Again, currently (Go 1.21), the types of interface values must be basic interface types.
+Again, currently (Go 1.22), the types of interface values must be basic interface types.
In the remaining contents of the current article,
when a value type is mentioned, the value type may be
non-interface type or a basic interface type.
diff --git a/pages/fundamentals/keywords-and-identifiers.html b/pages/fundamentals/keywords-and-identifiers.html
index bb0c258e..c1b97e74 100644
--- a/pages/fundamentals/keywords-and-identifiers.html
+++ b/pages/fundamentals/keywords-and-identifiers.html
@@ -12,7 +12,7 @@
Keywords
Keywords are the special words which help compilers understand and parse user code.
-Up to now (Go 1.21), Go has only 25 keywords:
+Up to now (Go 1.22), Go has only 25 keywords:
break default func interface select
case defer go map struct
@@ -94,7 +94,7 @@ Identifiers
The identifiers which don't start with an Unicode upper case letter are called non-exported identifiers.
The word non-exported can be interpreted as
private in many other languages.
-Currently (Go 1.21), eastern characters are viewed as non-exported letters.
+Currently (Go 1.22), eastern characters are viewed as non-exported letters.
Sometimes, non-exported identifiers are also called unexported identifiers.
diff --git a/pages/fundamentals/memory-block.html b/pages/fundamentals/memory-block.html
index 53620846..744bfea0 100644
--- a/pages/fundamentals/memory-block.html
+++ b/pages/fundamentals/memory-block.html
@@ -122,7 +122,7 @@ Where Will Memory Blocks Be Allocated On?
(Please note, there is a global limit of stack size each goroutine may reach.
If a goroutine exceeds the limit while growing its stack, the program crashes.
-As of Go Toolchain 1.21.n, the default maximum stack size
+As of Go Toolchain 1.22.n, the default maximum stack size
is 1 GB on 64-bit systems, and 250 MB on 32-bit systems.
We can call the SetMaxStack
function in the
runtime/debug
standard package to change the size.
@@ -283,7 +283,7 @@ Where Will Memory Blocks Be Allocated On?
-We will find that the two printed addresses are different (as of the standard Go compiler v1.21.n).
+We will find that the two printed addresses are different (as of the standard Go compiler v1.22.n).
diff --git a/pages/fundamentals/operators.html b/pages/fundamentals/operators.html
index aa252c94..b5a36796 100644
--- a/pages/fundamentals/operators.html
+++ b/pages/fundamentals/operators.html
@@ -965,8 +965,8 @@ More About Constant Expressions
The two named constants declared in the following program are not equal.
In the first declaration, both 3
and 2
are
-viewed as integers, however, they are both viewed as floating-point numbers
-in the second declaration.
+viewed as integers. However, in the second declaration,
+they are both viewed as floating-point numbers.
package main
diff --git a/pages/fundamentals/panic-and-recover-more-newer.html b/pages/fundamentals/panic-and-recover-more-newer.html
index 8cfb7bcf..f512759b 100644
--- a/pages/fundamentals/panic-and-recover-more-newer.html
+++ b/pages/fundamentals/panic-and-recover-more-newer.html
@@ -229,7 +229,7 @@ Associating Panics of Function Calls
}
-The output (when the above program is compiled with the standard Go compiler v1.21.n):
+The output (when the above program is compiled with the standard Go compiler v1.22.n):
panic: 0
panic: 1
panic: 2
diff --git a/pages/fundamentals/panic-and-recover-more.html b/pages/fundamentals/panic-and-recover-more.html
index b2a2efca..f94d92a4 100644
--- a/pages/fundamentals/panic-and-recover-more.html
+++ b/pages/fundamentals/panic-and-recover-more.html
@@ -226,7 +226,7 @@ Associating Panics and Goexit Signals of Function Calls
}
-The output (when the above program is compiled with the standard Go compiler v1.21.n):
+The output (when the above program is compiled with the standard Go compiler v1.22.n):
panic: 0
panic: 1
panic: 2
diff --git a/pages/fundamentals/reflection.html b/pages/fundamentals/reflection.html
index 39204af9..008286a1 100644
--- a/pages/fundamentals/reflection.html
+++ b/pages/fundamentals/reflection.html
@@ -49,8 +49,15 @@ The reflect.Type
Type and Values
but the call will return a reflect.Type
value which represents the dynamic type of the interface value.
In fact, the reflect.TypeOf
function has only one parameter of type interface{}
and always returns a reflect.Type
value which represents the dynamic type of the only interface parameter.
-Then how to get a reflect.Type
value which represents an interface type?
-We must use indirect ways which will be introduced below to achieve this goal.
+To get a reflect.Type
value which represents an interface type
+by using the reflect.TypeOf
function,
+we must use indirect ways which will be introduced below to achieve this goal.
+
+
+
+Since Go 1.22, we can also use [the reflect.TypeFor
function](https://docs.go101.org/std/pkg/reflect.html#name-TypeFor)
+to get a reflect.Type
value which represents a type which is known
+at compile time. The compile-time known type may be either non-interface or interface.
@@ -306,7 +313,7 @@
The reflect.Type
Type and Values
-Note, up to now (Go 1.21), there are no ways to create interface types through reflection.
+Note, up to now (Go 1.22), there are no ways to create interface types through reflection.
This is a known limitation of Go reflection.
diff --git a/pages/fundamentals/string.html b/pages/fundamentals/string.html
index cde488f7..46e80ca4 100644
--- a/pages/fundamentals/string.html
+++ b/pages/fundamentals/string.html
@@ -358,7 +358,7 @@ Compiler Optimizations for Conversions Between Strings and Byte Slices
Above has mentioned that the underlying bytes in the conversions
between strings and byte slices will be copied.
The standard Go compiler makes some optimizations,
-which are proven to still work in Go Toolchain 1.21.n,
+which are proven to still work in Go Toolchain 1.22.n,
for some special scenarios to avoid the duplicate copies.
These scenarios include:
diff --git a/pages/fundamentals/tips.html b/pages/fundamentals/tips.html
index ec575696..cb70f3bd 100644
--- a/pages/fundamentals/tips.html
+++ b/pages/fundamentals/tips.html
@@ -152,7 +152,7 @@ Don't use value assignments with expressions interacting with each other.
-Currently (Go 1.21), there are
+Currently (Go 1.22), there are
some evaluation orders in a multi-value assignment are unspecified when the expressions involved in the multi-value assignment interact with each other.
So try to split a multi-value assignment into multiple single value assignments
if there are, or you can't make sure whether or not there are, dependencies between the involved expressions.
diff --git a/pages/fundamentals/type-embedding.html b/pages/fundamentals/type-embedding.html
index 1f008ffc..332066f9 100644
--- a/pages/fundamentals/type-embedding.html
+++ b/pages/fundamentals/type-embedding.html
@@ -61,7 +61,7 @@
What Does Type Embedding Look Like?
Which Types Can be Embedded?
-The current Go specification (version 1.21) says
+The current Go specification (version 1.22) says
diff --git a/pages/fundamentals/type-system-overview.html b/pages/fundamentals/type-system-overview.html
index 8406f4fc..cc278a5a 100644
--- a/pages/fundamentals/type-system-overview.html
+++ b/pages/fundamentals/type-system-overview.html
@@ -111,7 +111,7 @@ Fact: Kinds of Types
Besides these kinds, the unsafe pointer types introduced in the
unsafe
standard package
also belong to one kind of types in Go.
-So, up to now (Go 1.21), Go has 26 kinds of types.
+So, up to now (Go 1.22), Go has 26 kinds of types.
@@ -666,7 +666,7 @@ Concept: Directions of Channel Types
Fact: Types Which Support or Don't Support Comparisons
-Currently (Go 1.21), Go doesn't support comparisons
+Currently (Go 1.22), Go doesn't support comparisons
(with the ==
and !=
operators)
for values of the following types:
diff --git a/pages/fundamentals/unofficial-faq.html b/pages/fundamentals/unofficial-faq.html
index c2cdce15..b85ddd69 100644
--- a/pages/fundamentals/unofficial-faq.html
+++ b/pages/fundamentals/unofficial-faq.html
@@ -86,7 +86,7 @@
What does the compile error message non-name *** on left side of :=
mean?
-Up to now (Go 1.21), there is a mandatory rule
+Up to now (Go 1.22), there is a mandatory rule
for short variable declarations:
All items at the left side of :=
must be pure
@@ -541,7 +541,7 @@
-Currently (Go Toolchain 1.21), for the standard Go compiler:
+Currently (Go Toolchain 1.22), for the standard Go compiler:
-
There are no explicit ways to specify which functions should be inlined in user programs.
@@ -1203,7 +1203,7 @@
-In Go, up to now (Go 1.21), there are no values satisfy the third definition.
+In Go, up to now (Go 1.22), there are no values satisfy the third definition.
In other words, the third definition is not supported.
However, variables of zero-size types may be viewed de facto immutable (addressable) values.
diff --git a/pages/fundamentals/unsafe.html b/pages/fundamentals/unsafe.html
index 8d07bd6f..ba2427b1 100644
--- a/pages/fundamentals/unsafe.html
+++ b/pages/fundamentals/unsafe.html
@@ -194,7 +194,7 @@ About the unsafe
Standard Package
Please note, the print results shown in the comments are for the standard
-Go compiler version 1.21.n on Linux AMD64 architecture.
+Go compiler version 1.22.n on Linux AMD64 architecture.
@@ -313,7 +313,7 @@
About the unsafe
Standard Package
Unsafe Pointers Related Conversion Rules
-Currently (Go 1.21), Go compilers allow the following explicit conversions.
+Currently (Go 1.22), Go compilers allow the following explicit conversions.
-
A safe pointer can be explicitly converted to an unsafe pointer,
@@ -1175,13 +1175,13 @@
Pattern 6: convert a reflect.SliceHeader.Data
or reflect.
reflect
standard package are similar in that
they say the representations of the two struct types may change in a later release.
So the above valid examples using the two types may become invalid even if the unsafe rules keep unchanged.
-Fortunately, at present (Go 1.21), the two available mainstream Go compilers (the standard Go compiler and the gccgo compiler)
+Fortunately, at present (Go 1.22), the two available mainstream Go compilers (the standard Go compiler and the gccgo compiler)
both recognize the representations of the two types declared in the reflect
standard package.
The Go core development team also realized that the two types are inconvenient and error-prone,
-so the two types have been not recommended any more since Go 1.20 (they have been deprecated since Go 1.21).
+so the two types have been not recommended any more since Go 1.20 and they have been deprecated since Go 1.21.
Instead, we should try to use the unsafe.String
, unsafe.StringData
,
unsafe.Slice
and unsafe.SliceData
functions described earlier in this article.
diff --git a/pages/fundamentals/value-copy-cost.html b/pages/fundamentals/value-copy-cost.html
index bf5769e5..c5ab4fbc 100644
--- a/pages/fundamentals/value-copy-cost.html
+++ b/pages/fundamentals/value-copy-cost.html
@@ -31,7 +31,7 @@ Value Sizes
-Up to present (Go Toolchain 1.21.n), for the standard Go compiler (and gccgo),
+Up to present (Go Toolchain 1.22.n), for the standard Go compiler (and gccgo),
values of a specified type always have the same value size.
So, often, we call the size of a value as the size of the type of the value.
@@ -52,7 +52,7 @@ Value Sizes
The following table lists the value sizes of all kinds of types
-(for the standard Go compiler v1.21.n).
+(for the standard Go compiler v1.22.n).
In the table, one word means one native word,
which is 4 bytes on 32-bit architectures and 8 bytes on 64-bit architectures.
@@ -258,7 +258,7 @@ Value Copy Costs
Benchmark_Loop-4 424342 2708 ns/op
Benchmark_Range_OneIterVar-4 407905 2808 ns/op
-Benchmark_Range_TwoIterVar-4 214860 5222 ns/op
+Benchmark_Range_TwoIterVar-4 214860 3915 ns/op
@@ -266,7 +266,7 @@
Value Copy Costs
lower than the other two.
But please note that, some compilers might make special optimizations
to remove the performance differences between these forms.
-The above benchmark result is based on the standard Go compiler v1.21.n.
+The above benchmark result is based on the standard Go compiler v1.22.n.
From 5a004cf74af8b2fb96b771263a4aaad88f39b6d2 Mon Sep 17 00:00:00 2001
From: Go101 <22589241+go101@users.noreply.github.com>
Date: Mon, 18 Mar 2024 15:03:47 +0800
Subject: [PATCH 4/4] v1.22.a
---
README.md | 3 +-
pages/blog/101.html | 5 +-
...-for-loop-semantic-changes-in-go-1.22.html | 1053 +++++++++++++++++
pages/details-and-tips/100-updates.html | 16 +
pages/details-and-tips/100-updates.md | 4 +
pages/details-and-tips/101.html | 2 +-
pages/fundamentals/100-updates.html | 12 +
pages/fundamentals/100-updates.md | 7 +
pages/generics/333-about-go-generics.html | 2 +-
.../555-type-constraints-and-parameters.html | 14 +-
...tiations-and-type-argument-inferences.html | 6 +-
...ons-on-values-of-type-parameter-types.html | 26 +-
...-the-status-quo-of-go-custom-generics.html | 18 +-
pages/optimizations/0.0-acknowledgements.html | 6 +-
pages/optimizations/0.1-introduction.html | 21 +-
.../optimizations/0.3-memory-allocations.html | 10 +-
pages/optimizations/1-pointer.html | 4 +-
pages/optimizations/100-updates.html | 6 +
pages/optimizations/100-updates.md | 4 +
pages/optimizations/101.html | 2 +-
pages/optimizations/6-map.html | 4 +-
.../code/3-array-and-slice/copy-slice_test.go | 14 +-
pages/quizzes/loop-1.html | 6 +-
23 files changed, 1174 insertions(+), 71 deletions(-)
create mode 100644 pages/blog/2024-03-01-for-loop-semantic-changes-in-go-1.22.html
diff --git a/README.md b/README.md
index 681a04d4..f87447bc 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,7 @@ Currently, the following books are avaliable:
These books are expected to help gophers gain a deep and thorough understanding of Go
and be helpful for both beginner and experienced Go programmers.
-To get latest news of Go 101 books, please follow the official twitter account [@go100and1](https://twitter.com/go100and1)
-and join [the Go 101 Slack space](https://go-101.slack.com).
+To get latest news of Go 101 books, please follow the official twitter account [@go100and1](https://twitter.com/go100and1).
### Install, Update, and Read Locally
diff --git a/pages/blog/101.html b/pages/blog/101.html
index 60ce9aa1..ff20369a 100644
--- a/pages/blog/101.html
+++ b/pages/blog/101.html
@@ -5,7 +5,9 @@ Go 101 Blog
-
+-
+
for
-loop semantic changes in Go 1.22: be aware of the impact
+
-
Go built-in slice manipulations are incomplete
@@ -26,3 +28,4 @@ Go 101 Blog
+
diff --git a/pages/blog/2024-03-01-for-loop-semantic-changes-in-go-1.22.html b/pages/blog/2024-03-01-for-loop-semantic-changes-in-go-1.22.html
new file mode 100644
index 00000000..12fd1be3
--- /dev/null
+++ b/pages/blog/2024-03-01-for-loop-semantic-changes-in-go-1.22.html
@@ -0,0 +1,1053 @@
+for
Loop Semantic Changes in Go 1.22: Be Aware of the Impact
+
+
+Go 1.22 changed the semantics of for
loops, including both for-range
loops and traditional 3-clause for ..; ..; .. {...}
loops (which will be abbreviated as for;;
in the remaining content of this article).
+
+You should understand the semantic changes and understand the implications of these changes in order to write Go code which will behave as intended. Otherwise, your code may exhibit unexpected behavior.
+
+
+What are the changes?
+
+
+Specifically speaking, only the semantics of the for
loops which loop variables are declared within the loops are changed (we call such loop variables as freshly-declared loop variables in the remaining content). For example, in the following piece of code, the semantics of the former two loops are not changed, but the latter two ones are changed (from Go 1.21 to 1.22).
+
+
for k, v = range aContainer {...}
+ for a, b, c = f(); condition; statement {...}
+
+ for k, v := range aContainer {...}
+ for a, b, c := f(); condition; statement {...}
+
+
+
+The former two loops don't declare their respective loop variables, but the latter two do. That is the difference here. The semantics of the former two loops are not changed.
+
+Let's view a simple Go program which undergoes semantic change (and behavior change) from Go 1.21 to Go 1.22:
+
+//demo1.go
+package main
+
+func main() {
+ c, out := make(chan int), make(chan int)
+
+ m := map[int]int{1: 2, 3: 4}
+ for i, v := range m {
+ go func() {
+ <-c
+ out <- i+v
+ }()
+ }
+
+ close(c)
+
+ println(<-out + <-out)
+}
+
+
+
+We can install multiple Go toolchain versions to check the outputs. Here, I use the GoTV tool to (conveniently) choose Go toolchain versions.
+
+The outputs:$ gotv 1.21. run demo1.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run demo1.go
+14
+$ gotv 1.22. run demo1.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo1.go
+10
+
+
+
+The behavior difference is obvious:
+
+
+-
+prior to Go 1.22, it (more likely) printed
14
;
+
+
+-
+since Go 1.22, it (always) prints
10
.
+
+
+
+
+
+The reason for the difference:
+
+
+-
+prior to Go 1.22, every freshly-declared loop variable used in a
for
loop is shared by all iterations during executing the loop. The two new created goroutines are more likely executed after the execution of the for
loop, in which case, the final values of the i
and v
loop variables are 3
and 4
. (3+4) + (3+4)
gives 14
.
+
+
+-
+since Go 1.22, every freshly-declared loop variable used in a
for
loop will be instantiated as a distinctive instance at the start of each iteration. In other words, it is per-iteration scoped now. So the values of the i
and v
loop variables used in the two new created goroutines are 1 2
and 3 4
, respectively. (1+2) + (3+4)
gives 10
.
+
+
+
+
+
+Prior to Go 1.22, there is a data race condition present in the above program code, which should be a clear fact for a competent Go programmer. In order to avoid data race and get the same result as the new semantics, the loop in the program should be re-written as:
+
+ for i, v := range m {
+ i, v := i, v // this line is added
+ go func() {
+ <-c
+ out <- i+v
+ }()
+ }
+
+
+
+Under the new semantics, the added line becomes unnecessary. In fact, this is the main reason why the semantic changes were made in Go 1.22.
+
+Similarly, the following program also undergoes semantic/behavior change from Go 1.21 to Go 1.22:
+
+// demo2.go
+package main
+
+func main() {
+ c, out := make(chan int), make(chan int)
+
+ for i := 1; i <= 3; i++ {
+ go func() {
+ <-c
+ out <- i
+ }()
+ }
+
+ close(c)
+
+ println(<-out + <-out + <-out)
+}
+
+
+
+The outputs of the above program (similarly, for 1.21, it is more likely, and for 1.22, it is always):$ gotv 1.21. run demo2.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run demo2.go
+12
+$ gotv 1.22. run demo2.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo2.go
+6
+
+
+
+This article focuses on the details of the changes and impact of the changes, rather than the reasons behind them. For details on the approval process and reasons behind the changes, see
+
+
+-
+The first discussion: redefining for loop variable semantics: https://github.com/golang/go/discussions/56010.
+
+
+-
+The formal proposal issue thread: https://github.com/golang/go/issues/60078.
+
+
+-
+The proposal file itself: https://go.googlesource.com/proposal/+/master/design/60078-loopvar.md. (If you only care about the reasons, you can just read this one.)
+
+
+-
+The wiki page: https://go.dev/wiki/LoopvarExperiment, and the blog article: https://go.dev/blog/loopvar-preview. Both of them were created after the proposal was accepted, which means the experiment phase started after the proposal was accepted.
+
+
+-
+Go 1.22 release notes: https://go.dev/doc/go1.22, which claims the release maintains the Go 1 promise of compatibility. However, this is simply not the case (read below for reasons).
+
+
+
+
+
+
+The impact of the changes
+
+
+Personally, I think the rationale of the change to for-range
loops is well-justified. The new semantics of for-range
loops become more intuitive. The change only affects for k, v := range .. {...}
loops, in which the :=
symbol strongly suggests that the loop variables are per-iteration scoped. No implications are introduced. The impact of the change is almost positive.
+
+On the other hand, in my honest opinion, the rationale of the change to for;;
loops is insufficient. The main reason provided by the proposal makers is to make a consistency with for-range
loops (they are both for
loops). However, It's not intuitive at all to think the loop variables in the following alike loops are per-iteration scoped.
+
+for a, b, c := anExpression; aCondition; postStatement {
+ ... // loop body
+}
+
+
+
+The a, b, c := anExpression
statement is only executed once during the execution of the loop, so it is intuitive that the loop variables are only explicitly instantiated once during the execution of the loop. The new semantics make the the loop variables instantiated at each iteration, which means there must be some implicit code to do the job. This is true. Go 1.22+ specification says:
+
+
+
+Each iteration has its own separate declared variable (or variables). The variable used by the first iteration is declared by the init statement. The variable used by each subsequent iteration is declared implicitly before executing the post statement and initialized to the value of the previous iteration's variable at that moment.
+
+
+
+By the speficication, since Go 1.22, the loop shown above is actually equivalent to the following pseudo-code (Sorry, the new semantics are hard to explain in a clear and perfect way. None of Go official documentations ever successfully achieve this goal. Here, I have tried my best.):
+
+{
+ a_last, b_last, c_last := anExpression
+ pa_last, pb_last, pc_last = &a_last, &b_last, &c_last
+ first := true
+ for {
+ a, b, c := *pa_last, *pb_last, *pc_last
+ if first {
+ first = false
+ } else {
+ postStatement
+ }
+ if !(aCondition) {
+ break
+ }
+ pa_last, pb_last, pc_last = &a, &b, &c
+ ... // loop body
+ }
+}
+
+
+
+Wow, quite a lot of magical implicit code. For a language that promotes explicitness, it's embarrassing.
+
+Implicitness often leads to unexpected surprises, which is not a surprise. The following will show several examples which might break your expectations.
+
+
+The behaviors of deferred function calls which capture loop variables might change
+
+
+A simple example:
+
+// demo-defer.go
+package main
+
+import "fmt"
+
+func main() {
+ for counter, n := 0, 2; n >= 0; n-- {
+ defer func(v int) {
+ fmt.Print("#", counter, ": ", v, "\n")
+ counter++
+ }(n)
+ }
+}
+
+
+
+Its outputs:$ gotv 1.21. run demo-defer.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run demo-defer.go
+#0: 0
+#1: 1
+#2: 2
+$ gotv 1.22. run demo-defer.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo-defer.go
+#0: 0
+#0: 1
+#0: 2
+
+
+
+You can find that, since Go 1.22, the value of counter
is never effectively increased. Why? I'm sorry. As mentioned above, it is some hard to clearly explain the new semantics and I don't think I have the ability to do this. You may get it from the following equivalent code:
+
+func main() {
+ counter_last, n_last := 0, 2
+ p_counter_last, p_n_last := &counter_last, &n_last
+ first := true
+ for {
+ counter, n := *p_counter_last, *p_n_last
+ if (first) {
+ first = false
+ } else {
+ n--
+ }
+
+ if !(n >= 0) {
+ break
+ }
+ p_counter_last, p_n_last = &counter, &n
+ defer func(v int) {
+ fmt.Print("#", counter, ": ", v, "\n")
+ counter++
+ }(n)
+ }
+}
+
+
+
+A more realistic example:
+
+// search.go
+package main
+
+import "fmt"
+
+func demoFilter(n int) bool {
+ return n & 1 == 0;
+}
+
+// Search values and return them without perverting order.
+func search(start, end int)(r []int) {
+ var count = 0
+ for i, index := start, 0; i <= end; i++ {
+ if demoFilter(i) {
+ count++
+ defer func(value int) {
+ r[index] = value
+ index++
+ }(i)
+ }
+ }
+
+ r = make([]int, count) // only allocate once
+ return
+}
+
+func main() {
+ fmt.Println(search(0, 9))
+}
+
+
+
+The outputs of the above program:$ gotv 1.21. run search.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run search.go
+[8 6 4 2 0]
+$ gotv 1.22. run search.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run search.go
+[0 0 0 0 0]
+
+
+
+So, since Go 1.22, just be careful when using freshly-declared loop variables in deferred function calls.
+
+
+
+The above example suggests that some freshly-declared loop variables in for;;
loops are expected to be per-iteration scoped, and some ones are expected to be whole-loop scoped. I ever suggested to allow re-declaration statements as postStatement
of for;;
loops to explicitly specify which loop variables are per-iteration scoped. For example, in the following loop code, n
is per-iteration scoped but counter
is whole-loop scoped.
+
+
+
+for counter, n := 0, 2; n >= 0; n := n - 1 { ... }
+
+
+
+However, sadly, the suggestion was ignored totally.
+
+
+
+
+
+
+Be careful when capturing loop variables in closures
+
+
+An example:
+
+// demo-closure-1.go
+package main
+
+import "fmt"
+
+func main() {
+ var printN func()
+ for n := 0; n < 9; n++ {
+ if printN == nil {
+ printN = func() {
+ fmt.Println(n)
+ }
+ }
+ }
+ printN()
+}
+
+
+
+Its outputs:$ gotv 1.21. run demo-closure-1.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run demo-closure-1.go
+9
+$ gotv 1.22. run demo-closure-1.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo-closure-1.go
+0
+
+
+
+Prior to Go 1.22, what the printN
closure captures is the only instance of the loop variable, which final value is 9
. However, since Go 1.22, what the printN
closure captures is the first instance of the loop variable, which final value is 1
. That is the reason of the behavior difference between the two Go versions.
+
+Here is a similar example:
+
+// demo-closure-2.go
+package main
+
+import (
+ "bytes"
+ "fmt"
+)
+
+func main() {
+ var printBuf func()
+ for buf, i := (bytes.Buffer{}), byte('a'); i <= 'z'; i++ {
+ if printBuf == nil {
+ printBuf = func() {
+ fmt.Printf("%s\n", buf.Bytes())
+ }
+ }
+ buf.WriteByte(i)
+ }
+ printBuf()
+}
+
+
+
+Its outputs:$ gotv 1.21. run demo-closure-2.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run demo-closure-2.go
+abcdefghijklmnopqrstuvwxyz
+$ gotv 1.22. run demo-closure-2.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo-closure-2.go
+a
+
+
+
+The third example:
+
+package main
+
+func main() {
+ var next func()
+ for i := 0; i < 3; next() {
+ print(i)
+ next = func() {
+ i++
+ }
+ }
+}
+
+
+
+It will never exit since Go 1.22 (prior to Go 1.22, it prints 012
then exits immediately)
+
+
+
+So, whether you think it is intuitive or not, just remember that, since Go 1.22, a freshly-declared loop variable may have many instances at run time, whether or not it is modified in postStatement
. Each of the instances is instantiated in one iteration.
+
+
+
+
+Be careful when taking addresses of loop variables
+
+
+Similarly, since Go 1.22, it may be dangerous to use the address of a freshly-declared loop variable across loop iterations.
+
+For example, what does the following Go program print? (Some people say this example is so bizarre that backward-compatibility should not be kept for such cases. What a ridiculous point. The code in reality may be more bizarre than this!)
+
+// demo-pointer1.go
+package main
+
+import "fmt"
+
+func main() {
+ for i, p := 0, (*int)(nil); p == nil; fmt.Println(p == &i) {
+ p = &i
+ }
+}
+
+
+
+Its outputs:$ gotv 1.21. run demo-pointer1.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run demo-pointer1.go
+true
+$ gotv 1.22. run demo-pointer1.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo-pointer1.go
+false
+
+
+
+Go 1.21 and 1.22 give different answers. Why? From the equivalent code shown below, we can get that, in the comparison p == &i
, p
points to the first instance of i
, whereas &i
takes the address of the second instance of i
. So the comparison evaluation result is false
.
+
+func main() {
+ i_last, p_last := 0, (*int)(nil)
+ p_i_last, p_p_last := &i_last, &p_last
+ first := true
+ for {
+ i, p := *p_i_last, *p_p_last
+ if first {
+ first = false
+ } else {
+ fmt.Println(p == &i)
+ }
+ if !(p == nil) {
+ break
+ }
+ p_i_last, p_p_last = &i, &p
+ p = &i
+ }
+}
+
+
+
+Another example:
+
+// demo-pointer2.go
+package main
+
+import "fmt"
+
+func main() {
+ var p *int
+ for i := 0; i < 3; *p++ {
+ p = &i
+ fmt.Println(i)
+ }
+}
+
+
+
+Since Go 1.22, the above program will never exit (prior to Go 1.22, it will):$ gotv 1.21. run demo-pointer2.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run demo-pointer2.go
+0
+1
+2
+$ gotv 1.22. run demo-pointer2.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo-pointer2.go
+0
+0
+0
+...
+
+
+
+
+Be careful when moving the 3rd clause statements inside loop bodies
+
+
+Since Go 1.22, the following two loops might be not equivalent with each other any more (prior to Go 1.22, they are equivalent).
+
+for ...; ...; postStatement {
+ ... // no continue statements here
+}
+
+for ...; ...; {
+ ... // no continue statements here
+ postStatement
+}
+
+
+
+For example, if we move the 3rd clause statements of the loops in the last section into loop bodies, then their behaviors change (since Go 1.22).
+
+// demo-pointer3.go
+package main
+
+import "fmt"
+
+func pointerDemo1() {
+ for i, p := 0, (*int)(nil); p == nil; {
+ p = &i
+ fmt.Println(p == &i) // the old 3rd clause
+ }
+}
+
+
+func pointerDemo2() {
+ var p *int
+ for i := 0; i < 3; {
+ p = &i
+ fmt.Println(i)
+ *p++ // the old 3rd clause
+ }
+}
+
+func main() {
+ pointerDemo1();
+ pointerDemo2();
+}
+
+
+
+The new outputs:$ gotv 1.22. run demo-pointer3.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo-pointer3.go
+true
+0
+1
+2
+
+
+
+
+Be careful when declaring no-copy values as loop variables
+
+
+As explained above, since Go 1.22, at the start of each loop iteration, each freshly-declared loop variable will get copied once, implicitly. The implication means that, since Go 1.22, it is not a good idea to declare no-copy values as loop variables, such as sync.Mutex
, sync/atomic.Int64
, bytes.Buffer
, and strings.Builder
values etc.
+
+For example, in Go versions prior to 1.22, the following code was considered concurrently correct. However, starting with Go 1.22, this code is considered to have a concurrency issue, because the loop variable wg
will be (implicitly) copied at the start of each loop iteration.
+
+// demo-nocopy1.go
+package main
+
+import (
+ "sync"
+ "time"
+)
+
+func process() (wait func()) {
+ for wg, i := (sync.WaitGroup{}), 0; i < 3; i++ {
+ if (wait == nil) {
+ wait = wg.Wait
+ }
+
+ wg.Add(1)
+ go func(v int) {
+ defer wg.Done()
+ if (v > 0) {
+ time.Sleep(time.Second/8)
+ }
+ println(v)
+ }(i)
+ }
+ return
+}
+
+func main() {
+ process()()
+}
+
+
+
+Its outputs:$ gotv 1.21. run demo-nocopy1.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.8/bin/go run demo-nocopy1.go
+0
+2
+1
+$ gotv 1.22. run demo-nocopy1.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.1/bin/go run demo-nocopy1.go
+0
+$ gotv 1.22. vet demo-nocopy1.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.1/bin/go vet demo-nocopy1.go
+
+
+
+Note that the go vet
command in Go 1.22 toolchain can't catch such implicit duplication of no-copy values, regardless of whether the loop variable wg
is captured in the loop body or not.
+
+Certain no-copy
checks occur during run time. Let's view an example which uses strings.Builder
(each strings.Builder
value contains a pointer field which should point to itself):
+
+// demo-nocopy2.go
+package main
+
+import (
+ "fmt"
+ "strings"
+)
+
+const Debug = true
+
+func a2z(callback func(*strings.Builder)) string {
+ for b, i := (strings.Builder{}), byte('a'); ; i++ {
+ b.WriteByte(i)
+ if (Debug) { callback(&b) }
+ if i == 'z' {
+ return b.String()
+ }
+ }
+}
+
+func main() {
+ debugProcess := func(pb *strings.Builder) {
+ //fmt.Println(pb.String()) // do nothing
+ }
+ fmt.Println(a2z(debugProcess))
+}
+
+
+
+Run it with different Go toolchains, we get:$ gotv 1.21. run demo-nocopy2.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run demo-nocopy2.go
+abcdefghijklmnopqrstuvwxyz
+$ gotv 1.22. run demo-nocopy2.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo-nocopy2.go
+panic: strings: illegal use of non-zero Builder copied by value
+
+goroutine 1 [running]:
+...
+
+
+
+Yes, the run-time no-copy
check works. Since Go 1.22, when the loop variable of type strings.Builder
gets duplicated, a panic is created. Prior to Go 1.22, this duplication will not happen, so there will be no panic.
+
+Note that, starting from Go 1.22, a Go compiler might optimize freshly-declared loop variables to be instantiated only once for the entire loop, even though their semantics suggest per-iteration instantiation. This optimization occurs when the compiler determines that each instance of the loop variable is used solely within the corresponding iteration's lifetime.
+
+Let's change the Debug
constant in the above example to false
, then run the example again with the 1.22 toolchain.
+
+$ gotv 1.22. run demo-nocopy.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run demo-nocopy.go
+abcdefghijklmnopqrstuvwxyz
+
+
+
+We can find that no panic occurs now. Why? Because now the if (Debug) { callback(&b) }
line becomes into dead code so that the compiler thinks that each instance of the loop variable b
is used solely within the corresponding iteration's lifetime. So the loop variable b
is instantiated only once for the entire loop and no duplication happens for the only instance.
+
+However, the compiler is too smart to make a bad decision here. The compiler incorrectly implements the semantics. The example program should panic regardless of the value of the Debug
constant. While this specific case might be considered acceptable due to the lack of harmful consequences, it raises concerns about the potential for unexpected behavior in other scenarios.
+
+The safe advice is try not to declare no-copy values as loop variables. This is just a suggestion, not a mandatory rule, because copying no-copy values does not always cause damage (but the damage may be exposed later when the code is refactored in some way).
+
+
+Warning: the performance of your Go programs might be degraded silently
+
+
+Sometimes, a compiler is over smart; sometimes, it is not smart enough. For example, sometimes, the official standard compiler provided in Go toolchain 1.22 is unable to determine that each instance of a freshly-declared loop variable is used solely within the corresponding iteration's lifetime, so that the loop variable will be instantiated per iteration and each of its instances will be allocated on heap instead of stack. Even worse, if the size of the loop variable is large, then high duplication costs will be incurred. When these situations occur, the performance of the program will be degraded.
+
+Let's view an example, in which a large-size loop variable is used in the bar
function.
+
+// demo-largesize.go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+type Large [1<<10]byte
+
+func foo(constFunc func(*Large, int)) {
+ a := Large{}
+ for i := 0; i < len(a); i++ {
+ constFunc(&a, i)
+ }
+}
+
+func bar(constFunc func(*Large, int)) {
+ for a, i := (Large{}), 0; i < len(a); i++ {
+ constFunc(&a, i)
+ }
+}
+
+func main() {
+ readonly := func(x *Large, k int) {}
+ bench := func(f func(func(*Large, int))) time.Duration {
+ start := time.Now()
+ f(readonly)
+ return time.Since(start)
+ }
+ fmt.Println("foo time:", bench(foo))
+ fmt.Println("bar time:", bench(bar))
+}
+
+
+
+Its outputs:$ gotv 1.21. run aaa.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run aaa.go
+foo time: 3.573µs
+bar time: 3.267µs
+$ gotv 1.22. run aaa.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run aaa.go
+foo time: 3.819µs
+bar time: 552.246µs
+
+
+
+The benchmark results reveal a significant performance regression in the bar
function between Go 1.21 and 1.22. Why? Because, with the official standard Go compiler 1.22, the loop variable a
in the bar
function is duplicated in each iteration. Whereas in prior versions, such duplication is always needless.
+
+The performance degradation issue does not affect the correctness of the code logic, Therefore, it may not be detected in time.
+
+Suggestions to avoid such performance degradation issue:
+
+
+-
+Try not to declare large-size values as loop variables, even if the syntax allows.
+
+
+-
+In certain situations, consider declaring loop variables outside the loop itself to optimize performance. This is beneficial if you can guarantee that the variables don't need to be instantiated in each iteration.
+
+
+
+
+
+
+Warning: things might become more subtle than before when loop variables are used concurrently
+
+
+Firstly, let's view a simple program.
+
+// demo-concurency1.go
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+func main() {
+ var wg sync.WaitGroup
+ for i := 0; i < 3; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ fmt.Println(i)
+ }()
+ }
+ wg.Wait()
+}
+
+
+
+The above program is intended to print the values of the loop variable i
at each iteration. Prior to Go 1.22, there is a clear data race condition present in the program, because the loop variable i
is only instantiated once during the whole loop. All the new created goroutines will read the single instance but the main goroutine will modify it. The following outputs prove this fact:$ CGO_ENABLED=1 gotv 1.21. run -race demo-concurency1.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run -race demo-concurency1.go
+3
+3
+==================
+WARNING: DATA RACE
+...
+==================
+3
+
+
+
+Prior to Go 1.22, the fix is simple, just add an i := i
line at the start of the loop body. Go 1.22 fixes the specified data race problem by changing the semantics of for;;
loops, without modifying the old problematic code. This can be verified by the following outputs:$ CGO_ENABLED=1 gotv 1.22. run -race demo-concurency1.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run -race demo-concurency1.go
+1
+2
+0
+
+
+
+In fact, this is just the reason why Go 1.22 made the semantic change to for;;
loops. But is it worth it to fix such a small problem by introducing magical implicit code?
+
+The effect of the attempt to fix the problem by making semantic change is actually limited. Let's modify the above program a bit:
+
+// demo-concurency2.go
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+func main() {
+ var wg sync.WaitGroup
+ for i := 0; i < 3; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ i++ // <-- add this line
+ fmt.Println(i)
+ }()
+ }
+ wg.Wait()
+}
+
+
+
+Is the new code still data race free (with Go 1.22 semantics)? It looks good. Each new created goroutine just uses an exclusive copy of the loop variable i
. But the answer is "no", because there is an implicit assignment at the start of each iteration and the implicit assignment uses an instance of the loop variable as source value (a.k.a. the main goroutine reads it), however the instance is modified in a new created goroutine.
+
+The following outputs verify there is a data race condition present in the new code:$ CGO_ENABLED=1 gotv 1.22. run -race demo-concurency2.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run -race demo-concurency2.go
+==================
+WARNING: DATA RACE
+...
+==================
+2
+1
+3
+Found 1 data race(s)
+
+
+
+Prior to Go 1.22, the data race is clear and easily to detect. But since Go 1.22, things become more subtle and the data race is not very clear (because of the implicit code).
+
+You may have found that the data-racy program appears to produce the intended outputs. Yes, this is another problem of the semantic change. While the data-racy program's outputs may appear correct most of the time, this masks a deeper issue: the data race can remain undetected for a longer period. This can significantly delay the identification and resolution of the problem!
+
+More seriously, some old good concurrent code will become problematic. Here is an example:
+
+// demo-concurency3.go
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+const NumWorkers = 3
+
+func isGold(num uint64) bool {
+ return num & 0xFFFFF == 0
+}
+
+func main() {
+ var c = make(chan uint64)
+ var m sync.Mutex
+ for n, i := 0, uint64(0); n < NumWorkers; n++ {
+ go func() {
+ for {
+ m.Lock()
+ i++
+ v := i
+ m.Unlock()
+
+ if isGold(v) {
+ c <- v
+ }
+ }
+ }()
+ }
+
+ for n := range c {
+ fmt.Println("Found gold", n)
+ }
+}
+
+
+
+Run it with different toolchain versions, get the following outputs:$ CGO_ENABLED=1 gotv 1.21. run -race demo-concurency3.go
+[Run]: $HOME/.cache/gotv/tag_go1.21.7/bin/go run -race demo-concurency3.go
+Found gold 1048576
+Found gold 2097152
+Found gold 3145728
+...
+^C
+$ CGO_ENABLED=1 gotv 1.22. run -race demo-concurency3.go
+[Run]: $HOME/.cache/gotv/tag_go1.22.0/bin/go run -race demo-concurency3.go
+==================
+WARNING: DATA RACE
+...
+==================
+Found gold 1048576
+Found gold 1048576
+Found gold 1048576
+Found gold 2097152
+Found gold 2097152
+Found gold 2097152
+Found gold 3145728
+Found gold 3145728
+Found gold 3145728
+...
+^C
+
+
+
+😳😳😳... (Consider that the title of the proposal to make the semantic change is "Proposal: Less Error-Prone Loop Variable Scoping".)
+
+How to fix the new data-racy code mentioned in this section? We can still use the old trick: just add an i := i
line at the start of the loop body. Yes, this is still the best suggestion to avoid data race for such situations in the Go 1.22+ era. Is this a mockery of the new semantics (of for;;
loops)?
+
+
+Advice and suggestions
+
+
+Okay, the above are the potential issues I've identified so far with the new for;;
semantics introduced in Go 1.22. There might be more, I'm not sure.
+
+Here are some recommendations you can follow in the Go 1.22+ era.
+
+
+Specify Go language versions for Go source files
+
+
+As demonstrated in many above examples, the semantic changes made in Go 1.22 break backward-compatibility. To reduce the damage as small as possible, Go core team decided to associate a Go language version to every Go source file. In other words, since Go 1.22, a Go source file = code + Go language version.
+
+There are several ways (in order of priority) to specify Go language versions for Go source files:
+
+
+-
+Add a
//go:build go1.xy
comment directive at the start of a source file to specify the language version for the source file. (However, the //go:build go1.xy
comment directive in a Go source file might still be ignored due to potential bugs in the toolchain implementation.)
+
+
+-
+Use
-gcflags=-lang=go1.xy
compiler option when building a Go binary to specify the Go language version for the seed files you're passing to the compiler.
+
+
+-
+Put a
go 1.xy
directive line in the go.mod
file of a Go module to specify the default Go language version for all of the source files in the Go module. A missing go directive line is assumed as go 1.16
. The effects of the directive line are described here.
+
+
+
+
+
+If the Go language version of a Go source file is not specified by all the above ways, then the version of the used Go compiler is used. In other words, the behavior of the code in the source file is compiler dependent.
+
+The design causes two problems:
+
+
+-
+Some people like to use the
go run
command to run Go code as scripts (a set of Go source files without go.mod
files). If a Go script source file doesn't contain a //go:build go1.xy
comment directive and the -gcflags=-lang=go1.xy
compiler option is not specified, then the behavior of the code in the script file is compiler dependent, just as what the above examples show.
+
+
+-
+If, for any reason (for example, to use a feature provided in newer Go versions), you upgrade the language version of a Go module that you maintain to 1.22 or higher, the behavior of the Go module might change. If the behavior changes are not detected in time (due to insufficient testing, etc.), then things may not go well.
+
+
+
+
+
+Anyway, since Go 1.22, you should try to specify a Go language version for every Go source file, in any of the above introduced ways, to avoid compiler version dependent behaviors. This is the minimum standard to be a professional Go programmer in the Go 1.22+ era.
+
+
+Upgrading module versions
+
+
+If you are maintaining a public Go module which are depended by other Go projects, please carefully check all the uses of for;;
loops in the module's code before bumping the language version to Go 1.22+ in the go.mod file. Especially pay attention to those freshly-declared loop variables which are not modified in postStatement
.
+
+If you upgrade dependency modules of your Go projects, pay attention to those ones which language versions os upgraded to Go 1.22 or higher from a version with the old semantics before Go 1.22.
+
+
+Avoid using freshly-declared loop variables in for;;
loops if you worry about getting bitten by the pitful of the new semantics
+
+
+Don't be too nervous, :D. In fact, most for;;
loops behave the same with either the old semantics or the new semantics. But if you're unsure about the new semantics, you can always rewrite the following alike loop using the old semantics:
+
+for a, b, c := anExpression; aCondition; postStatement {
+ ... // loop body
+}
+
+
+
+as
+
+{
+ a, b, c := anExpression
+ for ; aCondition; postStatement {
+ ... // loop body
+ }
+}
+
+
+
+to avoid triggering the new semantics. You can even specify which loop variables are instantiated per loop iteration and which are not. For example, in the following code, a
and c
are instantiated per loop iteration, but b
will be only instantiated once during the whole loop.
+
+{
+ a, b, c := anExpression
+ for ; aCondition; postStatement {
+ a, c := a, c
+ ... // loop body
+ }
+}
+
+
+
+This is a little awkward, but it is much safer.
+
+
+Final words
+
+
+Overall, I find the impact of the new semantics of for-range
loops is positive, while the impact of the new semantics of for;;
loops is negative. This is just my personal opinion.
+
+For magical implicitness is introduced, the new semantics of for;;
loops might require additional debug time in code writing and additional cognitive effort during code review and understanding in some cases.
+
+The new semantics of for;;
loops might introduce potential performance degradation and data race issues in existing code, requiring careful review and potential adjustments. Depending on specific cases, such issues might be found in time or not.
+
+In my honest opinion, the benefits of the new semantics of for;;
loops are rare and tiny, whereas the drawbacks are more prominent and serious.
+
+The semantic changes introduced in Go 1.22 significantly lower the threshold for maintaining backward compatibility. This is a bad start.
+
+I have expressed my opinions in the following comments:
+
+
+-
+https://github.com/golang/go/issues/60078#issuecomment-1541407014
+
+
+-
+https://github.com/golang/go/issues/60078#issuecomment-1544324607
+
+
+-
+https://github.com/golang/go/issues/60078#issuecomment-1547130632
+
+
+-
+https://github.com/golang/go/issues/60078#issuecomment-1558730767
+
+
+
+
+
+But the proposal makers (some members in the Go core team) totally ignored them and decided to proceed with the semantic change of for;;
loops anyway.
+
+What's done is done. In the end, I hope this article will help you write professional Go code in the Go 1.22+ era.
+
diff --git a/pages/details-and-tips/100-updates.html b/pages/details-and-tips/100-updates.html
index d6717069..a4ceb7f2 100644
--- a/pages/details-and-tips/100-updates.html
+++ b/pages/details-and-tips/100-updates.html
@@ -1,6 +1,22 @@
Go Details & Tips 101 Update History
+v1.22.a (2024/Mar/18)
+
+
+- Go 1.22 introduced
for range Integer
loops and changed the semantics of for-loop code blocks. Related contents are modified accordingly.
+
+
+v1.21.b
+
+
+- "Standard and User Packages Related" chapter:
+
+
+- due to https://github.com/golang/go/issues/57411, removed the "How to try to run a custom
init
function as early as possible?" tip.
+
+
+
v1.21.a (2023/OCT/11)
diff --git a/pages/details-and-tips/100-updates.md b/pages/details-and-tips/100-updates.md
index e2e87149..56eaeee4 100644
--- a/pages/details-and-tips/100-updates.md
+++ b/pages/details-and-tips/100-updates.md
@@ -1,6 +1,10 @@
# Go Details & Tips 101 Update History
+### v1.22.a (2024/Mar/18)
+
+* Go 1.22 introduced `for range Integer` loops and changed the semantics of for-loop code blocks. Related contents are modified accordingly.
+
### v1.21.b
* "Standard and User Packages Related" chapter:
diff --git a/pages/details-and-tips/101.html b/pages/details-and-tips/101.html
index 46c0adaf..1fbc5f06 100644
--- a/pages/details-and-tips/101.html
+++ b/pages/details-and-tips/101.html
@@ -15,7 +15,7 @@
-The book is Go 1.21 ready now (update history).
+The book is Go 1.22 ready now (update history).
diff --git a/pages/fundamentals/100-updates.html b/pages/fundamentals/100-updates.html
index 787635c4..476152e3 100644
--- a/pages/fundamentals/100-updates.html
+++ b/pages/fundamentals/100-updates.html
@@ -1,6 +1,18 @@
Go (Fundamentals) 101 Update History
+v1.22.a (2024/Mar/18)
+
+
+- Go 1.22 introduced
for range Integer
loops and changed the semantics of for-loop code blocks. Related articles are modified accordingly:
+
+
+
+
v1.21.a (2023/Oct/11)
diff --git a/pages/fundamentals/100-updates.md b/pages/fundamentals/100-updates.md
index 9ba96194..34b015e4 100644
--- a/pages/fundamentals/100-updates.md
+++ b/pages/fundamentals/100-updates.md
@@ -1,6 +1,13 @@
# Go (Fundamentals) 101 Update History
+### v1.22.a (2024/Mar/18)
+
+* Go 1.22 introduced `for range Integer` loops and changed the semantics of for-loop code blocks. Related articles are modified accordingly:
+ * [Basic Control Flows](https://go101.org/article/control-flows.html#for-semantic-change)
+ * [Goroutines, Deferred Function Calls and Panic/Recover](https://go101.org/article/control-flows-more.html#argument-evaluation-moment)
+ * [Arrays, Slices and Maps in Go](https://go101.org/article/container.html#iteration)
+
### v1.21.a (2023/Oct/11)
* Go 1.21 [added a `clear` builtin function](https://go101.org/article/container.html#clear).
diff --git a/pages/generics/333-about-go-generics.html b/pages/generics/333-about-go-generics.html
index e686e153..353087aa 100644
--- a/pages/generics/333-about-go-generics.html
+++ b/pages/generics/333-about-go-generics.html
@@ -29,7 +29,7 @@ About Go Custom Generics
The first Go version supporting custom generics is 1.18.
The type parameters proposal tries to solve many code reuse problems, but not all.
-And please note that, not all the features mentioned in the parameters proposal have been implemented yet currently (Go 1.21). The custom generics design and implementation will continue to evolve and get improved in future Go versions. And please note that the proposal is not the ceiling of Go custom generics.
+And please note that, not all the features mentioned in the parameters proposal have been implemented yet currently (Go 1.22). The custom generics design and implementation will continue to evolve and get improved in future Go versions. And please note that the proposal is not the ceiling of Go custom generics.
Despite the restrictions (temporary or permanent ones) in the current Go custom generics design and implementation,
I also have found there are some details which are handled gracefully and beautifully in the implementation.
diff --git a/pages/generics/555-type-constraints-and-parameters.html b/pages/generics/555-type-constraints-and-parameters.html
index 55ec1f57..f9fcafb5 100644
--- a/pages/generics/555-type-constraints-and-parameters.html
+++ b/pages/generics/555-type-constraints-and-parameters.html
@@ -250,7 +250,7 @@ Type sets and type implementations
are called basic interface types.
Before 1.18, Go only supports basic interface types.
Basic interfaces may be used as either value types or type constraints,
-but non-basic interfaces may only be used as type constraints (as of Go 1.21).
+but non-basic interfaces may only be used as type constraints (as of Go 1.22).
Take the types declared above as an example,, L
, M
, U
, Z
and any
are basic types.
@@ -296,7 +296,7 @@ More about the predeclared comparable
constraint
(Note, some earlier Go toolchain 1.18 and 1.19 versions failed to exclude [2]any
from the type set of C
. The bug has been fixed in newer Go toolchain 1.18 and 1.19 versions.)
-Currently (Go 1.21), the comparable
interface is treated as a non-basic interface type.
+
Currently (Go 1.22), the comparable
interface is treated as a non-basic interface type.
So, now, it may only be used as type parameter constraints, not as value types.
The following code is illegal:
@@ -394,7 +394,7 @@ Type implementation vs. type satisfactio
}
-
As of Go 1.21, type satisfactions are used to verify whether or not an ordinary value type
+
As of Go 1.22, type satisfactions are used to verify whether or not an ordinary value type
can be used as a type argument of an instantiation of a generic type/function.
Please read the next chapter for details.
@@ -403,7 +403,7 @@ More requirements for union terms
The above has mentioned that a union term may not be a type parameter. There are two other requirements for union terms.
The first is an implementation specific requirement: a term union with more than one term cannot contain the predeclared identifier comparable
or interfaces that have methods.
-For example, the following term unions are both illegal (as of Go toolchain 1.21):
+For example, the following term unions are both illegal (as of Go toolchain 1.22):
[]byte | comparable
string | error
@@ -605,7 +605,7 @@ Each type parameter is a distinct named
The type of a type parameter is a constraint, a.k.a an interface type.
This means the underlying type of a type parameter type should be an interface type.
However, this doesn't mean a type parameter behaves like an interface type.
-Its values may neither box non-interface values nor be type asserted (as of Go 1.21).
+Its values may neither box non-interface values nor be type asserted (as of Go 1.22).
In fact, it is almost totally meaningless to talk about underlying types of type parameters.
We just need to know that the underlying type of a type parameter is not itself.
And we ought to think that two type parameters never share an identical underlying type,
@@ -635,7 +635,7 @@
Each type parameter is a distinct named
}
-In fact, currently (Go 1.21), type parameters may not be embedded in struct types, too.
+In fact, currently (Go 1.22), type parameters may not be embedded in struct types, too.
The scopes of a type parameters
@@ -670,7 +670,7 @@ The scopes of a type parameters
func (E G[E]) Bar1() {} // error: E redeclared
-The following Bar2
method declaration should compile okay, but it doesn't now (Go toolchain 1.21). This is a bug which will be fixed in Go toolchain 1.21.
+The following Bar2
method declaration should compile okay, but it doesn't now (Go toolchain 1.22.n). This is a bug which will be fixed in future Go toolchain versions.
type G[G any] struct{x G} // okay
func (v G[G]) Bar2() {} // error: G is not a generic type
diff --git a/pages/generics/666-generic-instantiations-and-type-argument-inferences.html b/pages/generics/666-generic-instantiations-and-type-argument-inferences.html
index 4f47bb5b..cbecbdc2 100644
--- a/pages/generics/666-generic-instantiations-and-type-argument-inferences.html
+++ b/pages/generics/666-generic-instantiations-and-type-argument-inferences.html
@@ -227,7 +227,7 @@ Type argument inferences for generic function
Type argument inferences rules and implementation are still in the process of continuous improvement,
-from version to version. For example, the following code fails to compile before Go 1.21
+from version to version. For example, the following code failed to compile before Go 1.21
but it compiles okay since Go 1.21 (T
is inferred as float64
).
func Less[T ~int | ~float64](x, y T) bool {
@@ -290,7 +290,7 @@ Type argument inferences for generic function
Similarly, since Go 1.21, type arguments may be inferred
by analyzing method parameter types and result types.
-For example, the following code fails to compile before Go 1.21
+For example, the following code failed to compile before Go 1.21
but it compiles okay since Go 1.21.
package main
@@ -352,7 +352,7 @@ Type argument inferences for generic function
Type argument inferences don't work for generic type instantiations
-
Currently (Go 1.21), inferring type arguments of instantiated types from value literals is not supported.
+
Currently (Go 1.22), inferring type arguments of instantiated types from value literals is not supported.
That means the type argument list of a generic type instantiation must be always in full form.
For example, in the following code snippet, the declaration line for variable y
is invalid,
diff --git a/pages/generics/777-operations-on-values-of-type-parameter-types.html b/pages/generics/777-operations-on-values-of-type-parameter-types.html
index 8ca4c688..c13120b3 100644
--- a/pages/generics/777-operations-on-values-of-type-parameter-types.html
+++ b/pages/generics/777-operations-on-values-of-type-parameter-types.html
@@ -7,7 +7,7 @@
Operations on Values of Type Parameter Types
Within a generic function body,
an operation on a value of a type parameter is valid only if it is
valid for values of every type in the type set of the constraint of the type parameter.
-In the current custom generic design and implementation (Go 1.21),
+In the current custom generic design and implementation (Go 1.22),
it is not always vice versa.
Some extra requirements must be met to make the operation valid.
@@ -250,7 +250,7 @@ The core type of a type
A function is required to have a core type to be callable
-For example, currently (Go 1.21), in the following code, the functions foo
and bar
don't compile, bit the tag
function does.
+
For example, currently (Go 1.22), in the following code, the functions foo
and bar
don't compile, bit the tag
function does.
The reason is the F
type parameters in the foo
and bar
generic functions
both have not a core type, even
@@ -275,7 +275,7 @@ A function is required to have a core type to be callable
The type literal in a composite literal must have a core type
-For example, currently (Go 1.21), in the following code snippet,
+
For example, currently (Go 1.22), in the following code snippet,
the functions foo
and bar
compile okay, but the other ones don't.
func foo[T ~[]int] () {
@@ -304,7 +304,7 @@ An element index operation requires the container operand's type set not to
Otherwise, their element types must be identical.
The elements of strings are viewed as byte
values.
-
For example, currently (Go 1.21), in the following code snippet, only the functions foo
and bar
compile okay.
+For example, currently (Go 1.22), in the following code snippet, only the functions foo
and bar
compile okay.
func foo[T []byte | [2]byte | string](c T) {
_ = c[0] // okay
@@ -341,7 +341,7 @@ An element index operation requires the container operand's type set not to
A (sub)slice operation requires the container operand has a core type
-
For example, currently (Go 1.21), the following two functions both fail to compile,
+
For example, currently (Go 1.22), the following two functions both fail to compile,
even if the subslice operations are valid for all types in the corresponding type sets.
func foo[T []int | [2]int](c T) {
@@ -370,7 +370,7 @@ A (sub)slice operation requires the container operand has a core type
In a for-range
loop, the ranged container is required to have a core type
-For example, currently (Go 1.21), in the following code,
+
For example, currently (Go 1.22), in the following code,
only the last two functions, dot1
and dot2
, compile okay.
func values[T []E | map[int]E, E any](kvs T) []E {
@@ -472,7 +472,7 @@ In a for-range
loop, the ranged container is required to have a
optimized by the official standard Go compiler so that it doesn't duplicate
underlying bytes.
-
The following function doesn't compile now (Go 1.21),
+
The following function doesn't compile now (Go 1.22),
even if the types of the two iteration variables are always int
and rune
.
Whether or not it will compile in future Go versions is unclear.
@@ -489,7 +489,7 @@ Type parameter involved conversions
Firstly, we should know the conversion rules for ordinary types/values.
-By the current specification (Go 1.21),
+
By the current specification (Go 1.22),
given two types From
and To
, assume at least one of them is a type parameter,
then a value of From
can be converted to To
if a value of each type in
the type set of From
can be converted to each type in the type set of T
@@ -590,7 +590,7 @@
Type parameter involved assignments
In the following descriptions, the type of the destination value is called as the destination type, and the type of the source value is called as the source type.
-By the current specification (Go 1.21), for a type parameter involved assignment,
+By the current specification (Go 1.22), for a type parameter involved assignment,
- if the destination type is a type parameter and the source value is
@@ -772,7 +772,7 @@
A call to the predeclared new
function has not extra requiremen
A call to the predeclared make
function requires its first argument (the container type) has a core type
-
Currently (Go 1.21), in the following code snippet, the functions voc
and ted
both
+
Currently (Go 1.22), in the following code snippet, the functions voc
and ted
both
fail to compile, the other two compile okay.
The reason is the first argument of a call to the predeclared make
function
is required to have a core type.
@@ -880,11 +880,11 @@
About constraints with empty type sets
but they might affect the implementation perfection from theory view.
There are really several imperfections in the implementation
-of the current official standard Go compiler (v1.21.n).
+of the current official standard Go compiler (v1.22.n).
For example,
should the following function compile?
-It does with the latest official standard Go compiler (v1.21.n).
+It does with the latest official standard Go compiler (v1.22.n).
However, one of the above sections has mentioned that a make
call
requires its argument must have a core type.
The type set of the constraint C
declared in the following code
@@ -905,7 +905,7 @@
About constraints with empty type sets
This following is another example,
in which all the function calls in the function g
should compile okay.
However, two of them fail to compile with
-the latest official standard Go compiler (v1.21.n).
+the latest official standard Go compiler (v1.22.n).
func f1[T any](x T) {}
func f2[T comparable](x T) {}
diff --git a/pages/generics/888-the-status-quo-of-go-custom-generics.html b/pages/generics/888-the-status-quo-of-go-custom-generics.html
index e0d2bbbf..9d9ab6fe 100644
--- a/pages/generics/888-the-status-quo-of-go-custom-generics.html
+++ b/pages/generics/888-the-status-quo-of-go-custom-generics.html
@@ -7,7 +7,7 @@ The Status Quo of Go Custom Generics
Generic type aliases are not supported currently
-Currently (Go 1.21), a declared type alias may not have type parameters.
+
Currently (Go 1.22), a declared type alias may not have type parameters.
For example, in the following code, only the alias declaration for A
is legal,
the other alias declarations are all illegal.
@@ -27,7 +27,7 @@ Generic type aliases are not supported currently
Embedding type parameters is not allowed now
-Due to design and implementation complexities, currently (Go 1.21), type parameters are
+
Due to design and implementation complexities, currently (Go 1.22), type parameters are
disallowed to be embedded in either interface types or struct types.
For example, the following type declaration is illegal.
@@ -49,9 +49,9 @@ The method set of a constraint is not calculated completely for some cases
The method set of an interface type is the intersection of the method sets of each type in the interface's type set.
-However, currently (Go toolchain 1.21), only the methods explicitly specified in interface types are calculated into method sets.
+
However, currently (Go toolchain 1.22), only the methods explicitly specified in interface types are calculated into method sets.
For example, in the following code, the method set of the constraint should contain both Foo
and Bar
,
-and the code should compile okay, but it doesn't (as of Go toolchain 1.21).
+and the code should compile okay, but it doesn't (as of Go toolchain 1.22).
package main
@@ -77,7 +77,7 @@ The method set of a constraint is not calculated completely for some cases
No ways to specific a field set for a constraint
We know that an interface type may specify a method set.
-But up to now (Go 1.21), it could not specify a (struct) field set.
+But up to now (Go 1.22), it could not specify a (struct) field set.
There is a proposal for this: https://github.com/golang/go/issues/51259.
@@ -85,7 +85,7 @@ No ways to specific a field set for a constraint
No ways to use common fields for a constraint if the constraint has not a core (struct) type
-Currently (Go 1.21), even if all types in the type set of a constraint
+
Currently (Go 1.22), even if all types in the type set of a constraint
are structs and they share some common fields, the common fields still
could not be used if the structs don't share the identical underlying type.
@@ -131,7 +131,7 @@ No ways to use common fields for a constraint if the constraint has not a co
Fields of values of type parameters are not accessible
-
Currently (Go 1.21), even if a type parameter has a core struct type,
+
Currently (Go 1.22), even if a type parameter has a core struct type,
the fields of the core struct type still may not be accessed through
values of the type parameter.
For example, the following code doesn't compile.
@@ -156,7 +156,7 @@ Type switches on values of type parameters are not supported now
On the other hand, a type parameter has wave-particle duality.
For some situations, it acts as the types in its type set.
-Up to now (Go 1.21), values of type parameters may not be asserted.
+
Up to now (Go 1.22), values of type parameters may not be asserted.
The following two functions both fail to compile.
func tab[T any](x T) {
@@ -209,7 +209,7 @@ Type switches on values of type parameters are not supported now
Generic methods are not supported
-Currently (Go 1.21), for design and implementation difficulties,
+
Currently (Go 1.22), for design and implementation difficulties,
generic methods (not methods of generic types) are
not supported.
diff --git a/pages/optimizations/0.0-acknowledgements.html b/pages/optimizations/0.0-acknowledgements.html
index 227da8b9..644fd22f 100644
--- a/pages/optimizations/0.0-acknowledgements.html
+++ b/pages/optimizations/0.0-acknowledgements.html
@@ -1,11 +1,11 @@
Acknowledgments
-Firstly, thanks to the whole Go community. An active and responsive community guarantees this book is finished in time.
+Firstly, thanks to the entire Go community. An active and responsive community ensured this book was finished on time.
-Specially, I want to give thanks to the following people who helped me understand some implementation details in the official standard compiler and runtime: Keith Randall, Ian Lance Taylor, Axel Wagner, Cuong Manh Le, Michael Pratt, Jan Mercl, Matthew Dempsky, Martin Möhrmann, etc. I'm sorry if I forgot mentioning somebody in above lists. There are so many kind and creative gophers in the Go community that I must have missed out on someone.
+Specially, I want to give thanks to the following people who helped me understand some details in the official standard compiler and runtime implementations: Keith Randall, Ian Lance Taylor, Axel Wagner, Cuong Manh Le, Michael Pratt, Jan Mercl, Matthew Dempsky, Martin Möhrmann, etc. I'm sorry if I forgot mentioning somebody in the above list. There are so many kind and creative gophers in the Go community that I must have missed out on someone.
-I also would like to thank all gophers who ever made influences on the Go 101 book, be it directly or indirectly, intentionally or unintentionally.
+I also would like to thank all gophers who ever made influences on this book, be it directly or indirectly, intentionally or unintentionally.
Thanks to Olexandr Shalakhin for the permission to use one of the wonderful gopher icon designs as the cover image. And thanks to Renee French for designing the lovely gopher cartoon character.
diff --git a/pages/optimizations/0.1-introduction.html b/pages/optimizations/0.1-introduction.html
index 1df7de0f..b7d6fe43 100644
--- a/pages/optimizations/0.1-introduction.html
+++ b/pages/optimizations/0.1-introduction.html
@@ -1,18 +1,17 @@
About Go Optimizations 101
-This book provides some code performance optimization tricks, tips, and suggestions.
-Most of the contents in this book are made based on the official standard Go compiler and runtime implementation.
+This book offers practical tricks, tips, and suggestions to optimize Go code performance.
+Its insights are grounded in the official Go compiler and runtime implementation.
-Life is full of trade-offs, the programming world is, too.
-In programming, we often need to make trade-offs between code readability, maintainability, development efficiency, and program efficiency, etc.
-Even for one of the aspects, there are also trade-offs needing to be made.
-Taking program efficiency for an example, we might need to make trade-offs between memory saving, code execution speed, and implementation difficulty, etc.
+Life is full of trade-offs, and so is the programming world.
+In programming, we constantly balance trade-offs between code readability, maintainability, development efficiency, and performance, and even within each of these areas.
+For example, optimizing for performance often involves trade-offs between memory savings, execution speed, and implementation complexity.
-In practice, most parts of the code base of a project don't need to be implemented with high performances. Keeping them maintainable and readable is often more important (than making them memory saving and run very fast).
-The suggestions made in this book are just for the code parts which implementations really need to be high performant.
-Some of the suggestions often lead to more verbose code.
-And please note that some of the suggested implementations might be only performant at some certain scenarios.
+In real-world projects, most code sections don't demand peak performance.
+Prioritizing maintainability and readability generally outweighs shaving every byte or microsecond.
+This book focuses on optimizing critical sections where performance truly matters.
+Be aware that some suggestions might lead to more verbose code or only exhibit significant gains in specific scenarios.
The contents in this book include:
@@ -34,7 +33,7 @@ About Go Optimizations 101
Without particularly indicated, the code examples provided in this book are tested and run on a notebook with the following environment setup:
-go version go1.21.2 linux/amd64
+go version go1.22.1 linux/amd64
goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz
diff --git a/pages/optimizations/0.3-memory-allocations.html b/pages/optimizations/0.3-memory-allocations.html
index 40018242..5d3f028a 100644
--- a/pages/optimizations/0.3-memory-allocations.html
+++ b/pages/optimizations/0.3-memory-allocations.html
@@ -64,8 +64,8 @@ Memory wasting caused by allocated memory blocks larger than needed
In the official standard Go runtime implementation, for memory blocks allocated on heap,
-- some memory block size classes (no more than 32768 bytes) are predefined. As of the official standard Go compiler version 1.21.x, the smallest size classes are 8, 16, 24, 32, 48, 64, 80 and 96 bytes.
-- For memory blocks larger than 32768 bytes, each of them is always composed of multiple memory pages. The memory page size used by the official standard Go runtime (1.21 versions) is 8192 bytes.
+- some memory block size classes (no more than 32768 bytes) are predefined. As of the official standard Go compiler version 1.22.x, the smallest size classes are 8, 16, 24, 32, 48, 64, 80 and 96 bytes.
+- For memory blocks larger than 32768 bytes, each of them is always composed of multiple memory pages. The memory page size used by the official standard Go runtime (1.22 versions) is 8192 bytes.
So,
@@ -142,7 +142,7 @@ Memory wasting caused by allocated memory blocks larger than needed
And 44 (15 + 15 + 14) bytes are wasted during executing the Concat
function.
In the above example, the results of the string(s)
conversions are used temporarily in the string concatenation operation.
-By the current official standard Go compiler/runtime implementation (1.21 versions), the string bytes are allocated on heap (see below sections for details).
+By the current official standard Go compiler/runtime implementation (1.22 versions), the string bytes are allocated on heap (see below sections for details).
After the concatenation is done, the memory blocks carrying the string bytes become into memory garbage and will be collected eventually later.
Reduce memory allocations and save memory
@@ -266,7 +266,7 @@ Avoid unnecessary allocations by allocating enough in advance
}
-The outputs (for the official standard Go compiler v1.21.2):
+The outputs (for the official standard Go compiler v1.22.1):
Allocate from 0 to 2 (when append slice#0).
Allocate from 2 to 6 (when append slice#1).
@@ -277,7 +277,7 @@ Avoid unnecessary allocations by allocating enough in advance
From the outputs, we could get that only the last append
call doesn't allocate.
In fact, the Merge_TwoLoops
function could be faster in theory.
-As of the official standard Go compiler version 1.21, the make
call in the Merge_TwoLoop
function will zero all just created elements, which is actually unnecessary. Compiler optimizations in future versions might avoid the zero operation.
+As of the official standard Go compiler version 1.22, the make
call in the Merge_TwoLoop
function will zero all just created elements, which is actually unnecessary. Compiler optimizations in future versions might avoid the zero operation.
BTW, the above implementation of the Merge_TwoLoops
function has an imperfection.
It doesn't handle the integer overflowing case.
diff --git a/pages/optimizations/1-pointer.html b/pages/optimizations/1-pointer.html
index 4034cb28..314066a4 100644
--- a/pages/optimizations/1-pointer.html
+++ b/pages/optimizations/1-pointer.html
@@ -3,7 +3,7 @@
Pointers
Avoid unnecessary nil array pointer checks in a loop
-There are some flaws in the current official standard Go compiler implementation (v1.21.n).
+
There are some flaws in the current official standard Go compiler implementation (v1.22.n).
One of them is some nil array pointer checks are not moved out of loops.
Here is an example to show this flaw.
@@ -164,7 +164,7 @@ The case in which an array pointer is a struct field
Avoid unnecessary pointer dereferences in a loop
-Sometimes, the current official standard Go compiler (v1.21.n) is not smart enough to generate assembly instructions in the most optimized way. We have to write the code in another way to get the best performance. For example, in the following code, the f
function is much less performant than the g
function.
+Sometimes, the current official standard Go compiler (v1.22.n) is not smart enough to generate assembly instructions in the most optimized way. We have to write the code in another way to get the best performance. For example, in the following code, the f
function is much less performant than the g
function.
// avoid-indirects_test.go
package pointers
diff --git a/pages/optimizations/100-updates.html b/pages/optimizations/100-updates.html
index 337611f8..e156ce5c 100644
--- a/pages/optimizations/100-updates.html
+++ b/pages/optimizations/100-updates.html
@@ -1,6 +1,12 @@
Go Optimizations 101 Update History
+v1.22.a (2024/Mar/18)
+
+
+- Go 1.22 changed the semantics of for-loop code blocks. Related articles are modified accordingly.
+
+
v1.21.a (2023/OCT/11)
diff --git a/pages/optimizations/100-updates.md b/pages/optimizations/100-updates.md
index ce752f0f..516940e6 100644
--- a/pages/optimizations/100-updates.md
+++ b/pages/optimizations/100-updates.md
@@ -1,6 +1,10 @@
# Go Optimizations 101 Update History
+### v1.22.a (2024/Mar/18)
+
+* Go 1.22 changed the semantics of for-loop code blocks. Related articles are modified accordingly.
+
### v1.21.a (2023/OCT/11)
* In "Stack and Escape Analysis" chapter, mentions `reflect.ValueOf` function calls will not always allocate and escale the values referenced by their arguments.
diff --git a/pages/optimizations/101.html b/pages/optimizations/101.html
index c91e7c04..17286bf9 100644
--- a/pages/optimizations/101.html
+++ b/pages/optimizations/101.html
@@ -14,7 +14,7 @@
-The book is Go 1.21 ready now (update history).
+The book is Go 1.22 ready now (update history).
diff --git a/pages/optimizations/6-map.html b/pages/optimizations/6-map.html
index 830341a2..50828ef9 100644
--- a/pages/optimizations/6-map.html
+++ b/pages/optimizations/6-map.html
@@ -18,7 +18,7 @@ Maps
Clear map entries
-
Before Go 1.21, we could use the following loop to clear all entries in a map:
+We could use the following loop to clear all entries in a map:
for key := range aMap {
delete(aMap, key)
@@ -265,7 +265,7 @@ Use index tables instead of maps which key types have only a small set of po
If there are many such identical if-else
blocks used in code,
using maps with bool keys will reduce many boilerplates and make code look much cleaner.
For most use cases, this is generally good.
-However, as of Go toolchain v1.21.n, the map way is not very efficient from
+However, as of Go toolchain v1.22.n, the map way is not very efficient from
the code execution performance view.
The following benchmarks show the performance differences.
diff --git a/pages/optimizations/code/3-array-and-slice/copy-slice_test.go b/pages/optimizations/code/3-array-and-slice/copy-slice_test.go
index 1ff08b9b..fe728232 100644
--- a/pages/optimizations/code/3-array-and-slice/copy-slice_test.go
+++ b/pages/optimizations/code/3-array-and-slice/copy-slice_test.go
@@ -2,7 +2,7 @@ package arrays
import "testing"
-const N = 64
+const N = 32
var r []byte
var s = make([]byte, N)
@@ -12,6 +12,12 @@ func init() {
r = make([]byte, N)
}
+func Benchmark_Copy2(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ copy2(r, s)
+ }
+}
+
func copy2(d, s []byte) {
*(*[N]byte)(d) = *(*[N]byte)(s)
}
@@ -22,12 +28,6 @@ func Benchmark_Copy(b *testing.B) {
}
}
-func Benchmark_Copy2(b *testing.B) {
- for i := 0; i < b.N; i++ {
- copy2(r, s)
- }
-}
-
func Benchmark_Copy_self(b *testing.B) {
for i := 0; i < b.N; i++ {
copy(s, s)
diff --git a/pages/quizzes/loop-1.html b/pages/quizzes/loop-1.html
index 38745332..5ac6e7e1 100644
--- a/pages/quizzes/loop-1.html
+++ b/pages/quizzes/loop-1.html
@@ -55,7 +55,7 @@ loop 1
Key points:
-
- Before Go 1.22, the iteration variables (here they are
i
and v
)
+ Prior to Go 1.22, the iteration variables (here they are i
and v
)
are shared by all loop steps. In other words, each iteration variable only has
one instance during the execution of the loop.
That means all the elements of y
@@ -63,7 +63,7 @@ loop 1
In the end, the variable i
is set as 2
,
and the variable v
is set as 9
.
- So, before Go 1.22, the output is 222 999.
+ So, prior to Go 1.22, the output is 222 999.
-
@@ -74,7 +74,7 @@
loop 1
Different from the above program, the following program always prints 012 987
,
-either before Go 1.22 or since Go 1.22.
+either prior to or since Go 1.22.
package main