Reputation: 13878
I need to populate a struct that has a member of type [8]uint8
. This needs be populated with a byte array of type []byte
initialized to length 8. The simplistic approach does not work:
Data: [8]uint8(RequestFrame(0x180, r)),
gives
cannot convert .. (type []byte) to type [8]uint8
Since both arrays are structurally identical it would be nice if this could be done with casting/assignment rather than copying?
Upvotes: 2
Views: 2691
Reputation: 55483
The problem with your "simplistic approach" is that a slice
(of any type) is a struct
-typed value consisting of a pointer
and two integers; the pointer contains the address of the
underlying (backing) data array, and the integers contain
what len()
and cap()
builtins return for that slice.
In other words, a slice is sort of a view into an array.
Then, in Go, there is no concept of a type cast; there are only type conversions, and these conversions may only happen between types with the same underlying representation¹.
Since a slice and an array may not have the same underlying representation (array is literally a contiguous block of memory of the size just enough to contain all the array's elements), your alleged type conversion may not be legal.
There are two possible solutions.
The simplest is to just copy the data from the slice's backing array into a newly-allocated array:
var (
src = []byte{1, 2, 3, 4, 5, 6, 7, 8}
dst [8]uint8
)
copy(dst[:], src[:8])
Note that there exists an inherent disparity between slice an array types: an array type encodes both the type of its elements and its length (that is, the length is a part of the type), while a slice type only encodes the type of its elements (and may be of any length at runtime).
This means that you might need to have a check before such
copying that makes sure the source slice has exactly 8
elements, that is, len(src) == len(dst)
.
This invariant may be enforced by some other code, but I think
I'd warn you up front about this: if src
has less than 8
elements, the src[:8]
expression will panic at runtime,
and if it contains more, then there's the question of whether
copying just the first 8 of them is exactly what's needed.
The second approach (admittedly messier) is to just re-use the underlying array of the slice:
import "unsafe"
var (
src = []byte{1, 2, 3, 4, 5, 6, 7, 8}
dstPtr *[8]uint8
)
if len(src) != len(*dstPtr) {
panic("boom")
}
dstPtr = (*[8]uint8)(unsafe.Pointer(&src[0]))
Here, we've just taken the address of the first element
contained in the slice's underlying array and peformed
a "dirty" two-phase type-conversion to make the obtained
pointer to be of type *[8]uint8
—that is, "an address of
an array of 8 uint8
s".
Note two caveats:
The resulting pointer now points to the same memory block the original slice does. It means it's now possible to mutate that memory both through the slice and the pointer we obtained.
As soon as you'll decide to assign the array's data
to a variable of type [8]uint8
(and passing it as an argument
to a function's parameter of that type), you will dereference
that pointer (like with *dstPtr
), and at that moment
the array's data will be copied.
I'm specifically mentioning this as often people resort to hacks like this one to pull the backing array out of a slice precisely in an attempt to not copy the memory.
Copy the data (after supposedly verifying the
len(src) == len(dst)
invariant holds).
Copying 8 bytes is fast (on a typical 64-bit CPU this will be
a single MOV
instruction, or two at most), and the code will
be straightforward.
Only resort to hacks from the second solution when you really need to optimize on some critical hot path. In that case, comment the solution extensively and watch for not accidentally dereferencing your pointer.
¹ There are notable exceptions to this rule:
[]byte
is type-convertible to string
, and vice-versa.string
is type-convertible to []rune
, and vice-versa.int
is type-convertible to string
(but since Go 1.15 go vet
gives a warning about it, and this feature may probably be prohibited in the future).Upvotes: 4
Reputation: 44767
Starting from Go 1.17 you are able to use type conversion directly, from a slice to an array pointer:
a := make([]byte, 8)
b := (*[8]uint8)(a) // b is pointer to [8]uint8
The you can just dereference to obtain a non-pointer [8]uint8
type.
a := make([]byte, 8)
b := *(*[8]uint8)(a) // b is [8]uint8
Notes:
copy
, the conversion approach does not incur in extra allocations (not yours, nor any possibly done by copy
), because it simply yields a pointer to the existing backing array. Though dereferencing the array pointer will make a copy.a := make([]byte, 5)
b := (*[10]byte)(a) // panics
a := []byte{0xa1, 0xa2}
b := (*[2]uint8)(a)
fmt.Printf("%x\n", a[0]) // a1
b[0] = 0xff
fmt.Printf("%x\n", a[0]) // ff
byte
to uint8
, including type literals derived from them, because byte is an alias (identical to) of uint8.Related: How do you convert a slice into an array?
Upvotes: 1
Reputation: 3734
You can copy the contents of your byte
slice into your uint8
array very simply by using copy
, like this:
package main
import (
"fmt"
)
func main() {
slice := []byte{1, 2, 3, 4, 5, 6, 7, 8}
array := [8]uint8{}
copy(array[:], slice)
fmt.Println(array)
}
Outputs
[1 2 3 4 5 6 7 8]
But may I ask why you are using an array? It's usually better to just use slices, unless you have a really good reason.
Upvotes: 2