Skip to content

Commit 12998d3

Browse files
committed
chore: use go 1.20 idiomatic string<->byte conversion
As of Go 1.20, this is the idiomatic way to convert from Bytes to String and vice-versa. This updates `go.mod` to 1.20, builds have already been running on 1.23.x and 1.24.x since redis#3274.
1 parent 9c1655e commit 12998d3

File tree

3 files changed

+161
-8
lines changed

3 files changed

+161
-8
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/redis/go-redis/v9
22

3-
go 1.18
3+
go 1.20
44

55
require (
66
github.com/bsm/ginkgo/v2 v2.12.0

internal/util/unsafe.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,10 @@ import (
88

99
// BytesToString converts byte slice to string.
1010
func BytesToString(b []byte) string {
11-
return *(*string)(unsafe.Pointer(&b))
11+
return unsafe.String(unsafe.SliceData(b), len(b))
1212
}
1313

1414
// StringToBytes converts string to byte slice.
1515
func StringToBytes(s string) []byte {
16-
return *(*[]byte)(unsafe.Pointer(
17-
&struct {
18-
string
19-
Cap int
20-
}{s, len(s)},
21-
))
16+
return unsafe.Slice(unsafe.StringData(s), len(s))
2217
}

internal/util/unsafe_test.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package util
2+
3+
import (
4+
"reflect"
5+
"runtime"
6+
"sync"
7+
"testing"
8+
)
9+
10+
var (
11+
_tmpBytes []byte
12+
_tmpString string
13+
)
14+
15+
func TestBytesToString(t *testing.T) {
16+
tests := []struct {
17+
input string
18+
expect string
19+
}{
20+
{
21+
input: "string",
22+
expect: "string",
23+
},
24+
{
25+
input: "",
26+
expect: "",
27+
},
28+
}
29+
30+
for _, tt := range tests {
31+
t.Run(tt.input, func(t *testing.T) {
32+
input := []byte(tt.input)
33+
if result := BytesToString(input); !reflect.DeepEqual(tt.expect, result) {
34+
t.Errorf("BytesToString: Expected = %v, Got = %v", tt.expect, result)
35+
}
36+
37+
if len(tt.input) == 0 {
38+
return
39+
}
40+
41+
input[0] = 'x'
42+
if result := BytesToString(input); reflect.DeepEqual(tt.expect, result) {
43+
t.Errorf("BytesToString: expected not equal: %v", tt.expect)
44+
}
45+
})
46+
}
47+
}
48+
49+
func TestStringToBytes(t *testing.T) {
50+
tests := []struct {
51+
input string
52+
expect []byte
53+
}{
54+
{
55+
input: "string",
56+
expect: []byte("string"),
57+
},
58+
{
59+
input: "",
60+
expect: nil,
61+
},
62+
}
63+
64+
for _, tt := range tests {
65+
t.Run(tt.input, func(t *testing.T) {
66+
if result := StringToBytes(tt.input); !reflect.DeepEqual(tt.expect, result) {
67+
t.Errorf("StringToBytes: Expected = %v, Got = %v", tt.expect, result)
68+
}
69+
})
70+
}
71+
}
72+
73+
func TestBytesToStringGC(t *testing.T) {
74+
var (
75+
expect = t.Name()
76+
x string
77+
wg sync.WaitGroup
78+
)
79+
80+
wg.Add(1)
81+
go func() {
82+
defer wg.Done()
83+
tmp := append([]byte(nil), t.Name()...)
84+
x = BytesToString(tmp)
85+
}()
86+
wg.Wait()
87+
88+
for i := 0; i < 100; i++ {
89+
runtime.GC()
90+
}
91+
92+
if !reflect.DeepEqual(expect, x) {
93+
t.Errorf("Expected = %v, Got = %v", expect, x)
94+
}
95+
}
96+
97+
func TestStringToBytesGC(t *testing.T) {
98+
var (
99+
expect = []byte(t.Name())
100+
x []byte
101+
wg sync.WaitGroup
102+
)
103+
104+
wg.Add(1)
105+
go func() {
106+
defer wg.Done()
107+
tmp := append([]byte(nil), t.Name()...)
108+
x = StringToBytes(string(tmp))
109+
}()
110+
wg.Wait()
111+
112+
for i := 0; i < 100; i++ {
113+
runtime.GC()
114+
}
115+
if !reflect.DeepEqual(expect, x) {
116+
t.Errorf("Expected = %v, Got = %v", expect, x)
117+
}
118+
}
119+
120+
func BenchmarkStringToBytes(b *testing.B) {
121+
input := b.Name()
122+
123+
b.Run("copy", func(b *testing.B) {
124+
b.ReportAllocs()
125+
126+
for i := 0; i < b.N; i++ {
127+
_tmpBytes = []byte(input)
128+
}
129+
})
130+
131+
b.Run("unsafe", func(b *testing.B) {
132+
b.ReportAllocs()
133+
134+
for i := 0; i < b.N; i++ {
135+
_tmpBytes = StringToBytes(input)
136+
}
137+
})
138+
}
139+
140+
func BenchmarkBytesToString(b *testing.B) {
141+
input := []byte(b.Name())
142+
143+
b.Run("copy", func(b *testing.B) {
144+
b.ReportAllocs()
145+
146+
for i := 0; i < b.N; i++ {
147+
_tmpString = string(input)
148+
}
149+
})
150+
151+
b.Run("unsafe", func(b *testing.B) {
152+
b.ReportAllocs()
153+
154+
for i := 0; i < b.N; i++ {
155+
_tmpString = BytesToString(input)
156+
}
157+
})
158+
}

0 commit comments

Comments
 (0)