I'm trying to initialize a byte array as a mix of numbers and a string. I've managed to do it using a pair of appends, or by splitting the string into individual chars, but for the sake of readability is there a way to do it as a single initializer containing a string?
// method 1, with two appends
a := []byte{1, 2}
a = append(a, []byte("foo")...);
a = append(a, 3, 4);
// method 2, splitting the string into chars
b := []byte{1, 2, 'f', 'o', 'o', 3, 4)
What you did in your first attempt in 3 lines is probably more readable that any one-liners:
(You can try all the examples on the Go Playground.)
// Doing one-by-one:
a := []byte{1, 2}
a = append(a, []byte("foo")...)
a = append(a, 3, 4)
fmt.Println(a)
// Using individual chars:
a = []byte{1, 2, 'f', 'o', 'o', 3, 4}
fmt.Println(a)
// Using a single string literal:
a = []byte("\x01\x02foo\x03\x04")
fmt.Println(a)
// Using several "nested" appends:
a = append(append([]byte{1, 2}, []byte("foo")...), 3, 4)
fmt.Println(a)
Except if you create a helper function:
func concat(s ...[]byte) []byte {
var res []byte
for _, v := range s {
res = append(res, v...)
}
return res
}
And then using it:
// With a utility function:
a = concat([]byte{1, 2}, []byte("foo"), []byte{3, 4})
fmt.Println(a)
// With a utility function, formatted differently:
a = concat(
[]byte{1, 2},
[]byte("foo"),
[]byte{3, 4},
)
fmt.Println(a)
You could also do it using a single keyed composite literal and a single copy()
call to "insert" the string:
// With keyed literal and copy:
a = []byte{1, 2, 5: 3, 4}
copy(a[2:], "foo")
fmt.Println(a)
Still I don't think it's more readable or worth it.
As per comments left below, @EliasVanOotegem benchmarked the solution above (using append on an empty slice) and compared it to summing the total capacity of the byte slice required and allocating that memory in one go. The latter turns out to be slightly more efficiend (~20%), so I'll include that version below:
func concat(s ...[]byte) []byte {
c := 0
for _, v := range s {
c += len(v)
}
res := make([]byte, 0, c) // allocate everything
for _, v := range s {
res = append(res, v...)
}
return res
}
I personally would use the following optimized version which does not require slice header assignments as it uses the builtin copy()
:
func concat(s ...[]byte) []byte {
size := 0
for _, v := range s {
size += len(v)
}
res, i := make([]byte, size), 0
for _, v := range s {
i += copy(res[i:], v)
}
return res
}
I think I misunderstood what you are trying initially. You could write it as a string with hex embedded:
c := []byte("\x01\x02foo\x03\x04")
FWIW : it is possible to remove the cast to []byte(...)
in the append call :
a := []byte{65, 66}
a = append(a, "foo"...)
a = append(a, 67, 68)
fmt.Printf("%s", a)
// outputs : ABfooCD