Skip to Content
Course content

147: Working with Byte Slices

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By this point in the course, you're comfortable with strings and slices. But in the real world—especially when you're dealing with network sockets, file systems, or any kind of binary protocol—you'll find that []byte is actually the "first-class citizen" of Go. Strings are great for text, but byte slices are where the actual work happens.

Why bother with []byte when I have strings?

The short answer is mutability. In Go, strings are immutable. If you want to change a single character in a string, you can't; you have to create an entirely new string. That's a nightmare for performance if you're processing a 10MB file. A byte slice, however, is just a window into a mutable array of bytes.

I usually reach for []byte whenever I'm doing "buffer work." For example, if I'm cleaning up some raw data coming off a wire, I can modify the slice in place without triggering the garbage collector every two seconds.

package main

import "fmt"

func main() {
    // Imagine this is a packet from a network socket
    data := []byte("USER_ID:12345")

    // I can change a byte directly by index
    data[0] = 'A' 
    
    fmt.Printf("Modified: %s\n", data) // Output: AUSER_ID:12345
}

Is there a performance hit when I convert between the two?

Yes, there is. When you do b := []byte(s) or s := string(b), Go allocates new memory and copies the data. It does this to ensure that the immutability of the string is preserved. If you could convert a string to a slice without a copy, you could change the slice and accidentally change the "immutable" string, which would break a lot of assumptions in the language.

If you're doing this in a tight loop—say, parsing thousands of small packets per second—these allocations will add up and slow you down. In those cases, I try to keep the data as a []byte for as long as possible. Only convert to a string at the very end when you need to print it or put it into a map key.

How do I handle binary data that isn't just text?

Remember that byte is just an alias for uint8. This means a byte slice isn't just for "text that happens to be bytes"; it's for any raw binary data. When I'm working with custom binary protocols, I stop thinking in terms of characters and start thinking in terms of hex values.

Take a look at how you might verify a "Magic Byte"—a common technique used in file formats to identify the file type immediately.

package main

import "fmt"

func main() {
    // A fake binary header: Magic Byte (0xAA), Version (0x01), Payload Length (0x04)
    packet := []byte{0xAA, 0x01, 0x04, 0xDE, 0xAD, 0xBE, 0xEF}

    if packet[0] != 0xAA {
        fmt.Println("Invalid packet: Magic byte mismatch")
        return
    }

    fmt.Printf("Packet version: %d, Payload length: %d\n", packet[1], packet[2])
}

Notice how I'm using hex literals like 0xAA. It's much more readable than using decimal numbers when you're dealing with raw bytes. If you see uint8 in a function signature, just know it's exactly the same thing as a byte.




📋 Practical Task

Build a Binary Packet Sanitizer

In this exercise, you will write a function that cleans up a raw byte slice. In some legacy protocols, "padding" bytes (null bytes 0x00) are added to the end of a message to fill a fixed-size buffer.

Your Task: Create a function called SanitizePacket that takes a []byte and returns a new slice with all trailing null bytes (0x00) removed. The function should not remove null bytes that appear in the middle of the data, only those at the very end.

Requirements:

  • The function signature must be: func SanitizePacket(data []byte) []byte
  • Test it with a slice like []byte{0x48, 0x69, 0x00, 0x21, 0x00, 0x00}. The result should be []byte{0x48, 0x69, 0x00, 0x21}.
  • Avoid converting the slice to a string to perform the cleanup; work with the bytes directly.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.