To supplement Stephen Weinberg's answer:
So, what are some real examples of "planning the detailed layout of memory" or "help avoid allocation" that slices would be unsuited for?
Here's an example for "planning the detailed layout of memory". There are many file formats. Usually a file format is like this: it starts with a "magic number" then follows an informational header whose structure is usually fixed. This header contains information about the content, for example in case of an image file it contains info like image size (width, height), pixel format, compression used, header size, image data offset and alike (basically describes the rest of the file and how to interpret / process it).
If you want to implement a file format in Go, an easy and convenient way is to create a struct
containing the header fields of the format. When you want to read a file of such format, you can use the binary.Read()
method to read the whole header struct
into a variable, and similarly when you want to write a file of that format, you can use binary.Write()
to write the complete header in one step into the file (or wherever you send the data).
The header might contain even tens or a hundred fields, you can still read/write it with just one method call.
Now as you can feel, the "memory layout" of the header struct
must match exactly the byte layout as it is saved (or should be saved) in the file if you want to do it all in one step.
And where do arrays come into the picture?
Many file formats are usually complex because they want to be general and so allowing a wide range of uses and functionality. And many times you don't want to implement / handle everything the format supports because either you don't care (because you just want to extract some info), or you don't have to because you have guarantees that the input will only use a subset or a fixed format (out of the many cases the file format fully supports).
So what do you do if you have a header specification with many fields but you only need a few of them? You can define a struct which will contain the fields you need, and between the fields you can use arrays with the size of the fields you just don't care / don't need. This will ensure that you can still read the whole header with one function call, and the arrays will basically be the placeholder of the unused data in the file. You may also use the blank identifier as the field name in the header struct
definition if you won't use the data.
Theoretical example
For an easy example, let's implement a format where the magic is "TGI" (Theoretical Go Image) and the header contains fields like this: 2 reserved words (16 bit each), 1 dword image width, 1 dword image height, now comes 15 "don't care" dwords then the image save time as 8-byte being nanoseconds since January 1, 1970 UTC.
This can be modeled with a struct like this (magic number excluded):
type TGIHeader struct {
_ uint16 // Reserved
_ uint16 // Reserved
Width uint32
Height uint32
_ [15]uint32 // 15 "don't care" dwords
SaveTime int64
}
To read a TGI file and print useful info:
func ShowInfo(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
magic := make([]byte, 3)
if _, err = f.Read(magic); err != nil {
return err
}
if !bytes.Equal(magic, []byte("TGI")) {
return errors.New("Not a TGI file")
}
th := TGIHeader{}
if err = binary.Read(f, binary.LittleEndian, &th); err != nil {
return err
}
fmt.Printf("%s is a TGI file,\n\timage size: %dx%d\n\tsaved at: %v",
name, th.Width, th.Height, time.Unix(0, th.SaveTime))
return nil
}