Right now answers are suggesting unsafe
, but they are not going over why you should not use unsafe
and what you should do instead. So let me give it a try.
In the C++ code you posted in OP, you seem to be writing a sort of binary format, which reads data through simple casting. Simple, but effective. The only obvious problem I can see is that it does not allow interoperability between Little Endian and Big Endian, but that's another matter.
The way you would approach binary encoding in Go is through the use of the handy package encoding/binary
, which is able to decode binary data directly into fixed-size structs (ie. without strings or slices, which are variable-length and so the length would need to be encoded arbitrarily).
Here is how I would implement your example in Go:
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
const textMessage = 11
func main() {
// Set up our data - this is an example of the data I
// imagine you want to decode.
r := bytes.NewReader([]byte{
// Header
byte(textMessageLen + headerLen), 0, 0, 0,
11,
// Body
137, 0, 0, 0,
117, 0, 0, 0,
// Message content
'H', 'e', 'l', 'l', 'o', '!', 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
})
// We first read the header to decide what to do next.
// Notice that we explicitly pass an argument that indicates to
// parse integers using little endian, making this code portable.
var h Header
err := binary.Read(r, binary.LittleEndian, &h)
if err != nil {
fmt.Println(err)
return
}
switch h.Type {
case textMessage:
// It's a text message - make sure the length is right.
if textMessageLen != (int(h.Length) - headerLen) {
fmt.Println("Invalid payload length")
return
}
// Decode the data
var t TextMessage
err = binary.Read(r, binary.LittleEndian, &t)
if err != nil {
fmt.Println(err)
return
}
// Print it out
fmt.Printf("Sender: %d; Receiver: %d
Message: %s
",
t.Sender, t.Receiver, bytes.TrimRight(t.Text[:], "\x00"))
default:
fmt.Println("unknown payload type")
}
}
// If you need to find out what the encoded size of a struct is, don't use unsafe.Sizeof;
// use binary.Size instead.
var headerLen = binary.Size(Header{})
type Header struct {
Length uint32
Type uint8
}
var textMessageLen = binary.Size(TextMessage{})
type TextMessage struct {
Sender, Receiver uint32
Text [64]byte
}
<kbd>Playground</kbd>
So, here are a few things to note:
- In Go, binary formats are usually NEVER implemented reading directly from memory. This is because 1. it's platform-dependent (Little/Big endian), 2. trouble with strings, slices and struct paddings and 3. it's, well, unsafe. If you don't tamper with memory directly, Go can pretty much guarantee that your program will run smoothly on any platform without any modifications. The moment you start doing that, you lose that guarantee.
- We don't need to "advance the pointer" of the data we're reading - we're passing down to
binary.Read
an io.Reader
, which means that when we read something from it the data read is discarded, so the pointer is automatically advanced.
- There are possible implications with the GC when playing with memory with yourself - the GC might think that a point in the data is no longer referenced and free to be used - whereas really you're still using it, just not clearly referencing using a native Go pointer.