Question

Convert a Byte Slice to a String in Go

gotypessha1

Question

How can I convert a Go byte slice ([]byte) to a string? I receive an error when attempting conversions such as:

string(data[:n])

For example, I want to use a SHA-1 value as part of a filename. Do I need to explicitly specify UTF-8 or another character encoding when converting bytes to a string?

Short Answer

In Go, convert a byte slice to a string with string(bytes). This is appropriate when the bytes represent text. For binary data such as a SHA-1 digest, encode the bytes as hexadecimal or Base64 first so the resulting string is safe and readable.

Concept

A []byte is a slice containing raw bytes. A string is an immutable sequence of bytes that is conventionally used to hold text.

The direct conversion is:

text := string(data)

This conversion does not decode, validate, or change the byte values. It creates a string containing the same bytes. Therefore, it is ideal when data contains textual data, such as UTF-8 JSON, a file line, or an HTTP response body.

However, not all bytes are text. A SHA-1 digest is binary data: its 20 bytes may contain control characters, slashes, null bytes, or invalid UTF-8 byte sequences. Converting that digest directly with string(sum[:]) is legal Go, but it is a poor filename or display value.

For binary values that must be displayed, logged, sent as text, or used in filenames, use a text encoding such as hexadecimal:

name := hex.EncodeToString(sum[:])

This produces only the characters 0-9 and a-f, making it readable and generally safe for filenames.

Mental Model

Think of bytes as sealed packages. A []byte is a tray of packages, and string(data) places the same packages into a label called a string. It does not inspect the contents or translate them into another language.

If the packages contain letters encoded as UTF-8, the label reads like normal text. If they contain random binary values from a hash, the label may contain invisible or unsafe characters.

Hex encoding is like replacing every package with a printable two-character code. The original data can still be recovered, but it is now safe to read and use in text-oriented places.

Take Quiz

Syntax and Examples

Use string(...) when the bytes already represent text.

package main

import "fmt"

func main() {
    data := []byte("Hello, Go!")
    text := string(data)

    fmt.Println(text)
}

Output:

Hello, Go!

You can also convert only part of a byte slice:

buffer := []byte("hello world")
n := 5
word := string(buffer[:n])

fmt.Println(word) // hello

The slice expression buffer[:n] must be valid: n must be between 0 and len(buffer).

Converting a fixed-size byte array

Hash functions often return an array, such as [20]byte for SHA-1. Slice the array before passing it to encoders:

sum := [3]byte{72, 105, 33}
text := string(sum[:])

fmt.Println(text) // Hi!

Encoding a SHA-1 digest for a filename

package main

import (
    "crypto/sha1"
    "encoding/hex"
    "fmt"
)

func main() {
    sum := sha1.Sum([]byte("report contents"))
    filename := hex.EncodeToString(sum[:]) + ".txt"

    fmt.Println(filename)
}

hex.EncodeToString converts each digest byte into two hexadecimal characters. A SHA-1 digest has 20 bytes, so its hexadecimal representation has 40 characters.

Step by Step Execution

Consider this example:

package main

import "fmt"

func main() {
    buffer := []byte{'G', 'o', '!', 'x'}
    n := 3
    message := string(buffer[:n])

    fmt.Println(message)
}

Step by step:

  1. buffer holds four byte values: G, o, !, and x.
  2. n is 3.
  3. buffer[:n] creates a slice containing indexes 0, 1, and 2: G, o, and !.
  4. string(buffer[:n]) creates the string "Go!" from those bytes.
  5. fmt.Println prints Go!.

Now compare a hash:

sum := sha1.Sum([]byte("Go"))
raw := string(sum[:])
encoded := hex.EncodeToString(sum[:])
  • raw contains the 20 original digest bytes. It can include non-printable bytes.
  • encoded contains a readable 40-character hexadecimal string.

Use encoded when the value is intended for a filename, URL component, JSON field, log, or user-visible output.

Real World Use Cases

  • Reading text files: string(fileBytes) after os.ReadFile when the file contains UTF-8 text.
  • HTTP request bodies: Convert JSON bytes to a string for debugging or pass bytes directly to a JSON decoder.
  • Buffered network reads: Convert buffer[:n] to a string after Read returns the number of bytes read.
  • Hash-based cache keys: Encode digests with hex.EncodeToString for stable cache keys and filenames.
  • API tokens and binary identifiers: Use Base64 or hex when binary values must travel through text-based JSON, headers, or URLs.
  • Log messages: Encode arbitrary binary payloads before logging so output remains readable and does not corrupt terminals or log files.
Take Quiz

Real Codebase Usage

Developers choose the conversion based on what the bytes mean.

Text input

When a function receives UTF-8 text as bytes, direct conversion is common:

body, err := io.ReadAll(resp.Body)
if err != nil {
    return err
}

fmt.Println(string(body))

Avoiding unnecessary conversions

Many APIs accept []byte directly. Keep data as bytes if no string is needed:

var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
    return err
}

There is no need for json.Unmarshal([]byte(string(body)), &payload).

Creating deterministic identifiers

A typical hash key helper uses a text encoding:

func contentKey(data []byte) string {
    sum := sha1.Sum(data)
    return hex.EncodeToString(sum[:])
}

Validation before slicing buffers

Code that uses buffer[:n] should ensure n is valid, especially when it comes from external input. When n comes directly from Read, it is safe as long as the returned error is handled according to that API's rules.

if n < 0 || n > len(buffer) {
    return fmt.Errorf("invalid byte count: %d", n)
}
text := string(buffer[:n])

For password storage, do not use SHA-1 as a password hash. Use a purpose-built password hashing approach instead. SHA-1 may still appear in legacy systems or non-security uses such as content identifiers, but it is not suitable for new security-sensitive designs.

Common Mistakes

Assuming string([]byte) performs encoding conversion

data := []byte{0xff, 0xfe}
text := string(data)

This compiles, but it does not turn arbitrary bytes into valid readable Unicode text. It preserves the bytes. Use a known decoder only when you know the source encoding, or use hex/Base64 for binary data.

Using raw hash bytes as a filename

sum := sha1.Sum([]byte("data"))
filename := string(sum[:]) // Legal, but unsuitable for a filename.

Use hex instead:

filename := hex.EncodeToString(sum[:])

Forgetting to slice an array for an API expecting []byte

sha1.Sum returns [20]byte, while hex.EncodeToString accepts []byte.

sum := sha1.Sum([]byte("data"))
// hex.EncodeToString(sum) // Does not compile.
encoded := hex.EncodeToString(sum[:])

Slicing beyond the buffer length

buffer := []byte("Go")
text := string(buffer[:5]) // Panics at runtime.

Keep the upper bound at or below len(buffer).

Confusing a string's byte length with character count

len(text) counts bytes, not human-visible characters. UTF-8 characters may use multiple bytes.

fmt.Println(len("é")) // 2 bytes in UTF-8

Use utf8.RuneCountInString when you specifically need a Unicode code point count.

Comparisons

SituationRecommended approachWhy
Bytes are known UTF-8 textstring(data)Direct, simple conversion to a Go string
Need a section of a byte buffer as textstring(buffer[:n])Uses only the bytes that were read
Fixed byte array contains textstring(array[:])Converts the array's slice to a string
Binary hash or arbitrary bytes need text formhex.EncodeToString(data)Readable and safe printable representation
Binary data needs a shorter text formbase64.StdEncoding.EncodeToString(data)More compact than hex, but uses additional characters
Need to parse JSON bytesjson.Unmarshal(data, &value)Avoids converting to string unnecessarily

Hex versus Base64

EncodingOutput size for 20-byte SHA-1 digestTypical use
Hex40 charactersFilenames, command-line output, logs, Git-like identifiers
Standard Base6428 characters including paddingJSON fields and compact transport

Hex is often the easier choice for filenames because its character set is limited to digits and lowercase letters.

Take Quiz

Cheat Sheet

// []byte to string: use when bytes are text.
text := string(data)

// Convert only the first n bytes.
text := string(buffer[:n])

// [N]byte array to string.
text := string(array[:])

// SHA-1 digest to printable text.
sum := sha1.Sum(data)
hexText := hex.EncodeToString(sum[:])

// Arbitrary bytes to Base64 text.
b64Text := base64.StdEncoding.EncodeToString(data)

Rules:

  • string(data) preserves byte values; it does not automatically decode an unknown character encoding.
  • Use direct conversion only when bytes represent text in the expected encoding, commonly UTF-8.
  • Use hex or Base64 for arbitrary binary data.
  • buffer[:n] requires 0 <= n <= len(buffer).
  • Hash functions commonly return fixed-size arrays; use sum[:] to obtain a byte slice.
  • A Go string can contain arbitrary bytes, including null bytes and invalid UTF-8.

FAQ

How do I convert []byte to string in Go?

Use string(data):

text := string(data)

Do I need to specify UTF-8 when converting bytes to a Go string?

No. The conversion copies the byte sequence into a string without selecting or applying an encoding. UTF-8 matters when you interpret the bytes as text: the source data must already be valid UTF-8 if you expect normal UTF-8 text.

Why does string(buffer[:n]) fail or panic?

It is valid syntax when buffer is a []byte or byte array. A runtime panic happens if n is negative or greater than len(buffer). A compile error may mean buffer is not a byte slice/array or the code is being used in an invalid context.

Can a Go string contain arbitrary binary data?

Yes. A string is a sequence of bytes and can hold arbitrary byte values. That does not make it appropriate for text output, paths, or filenames.

How should I convert a SHA-1 hash to a filename string?

Encode the digest as hexadecimal:

sum := sha1.Sum(data)
filenamePart := hex.EncodeToString(sum[:])

Why use sum[:] after sha1.Sum?

sha1.Sum returns a fixed-size [20]byte array. sum[:] creates a []byte slice, which functions such as hex.EncodeToString accept.

Is SHA-1 safe for passwords or security signatures?

No. Do not use SHA-1 for new security-sensitive designs, especially password hashing. Use a modern, purpose-specific cryptographic design instead.

Take Quiz

Mini Project

Description

Build a small utility that creates a safe filename from text content. The project demonstrates the difference between converting textual bytes directly and encoding binary SHA-1 digest bytes as hexadecimal.

Goal

Create a program that prints the original text and a deterministic, filename-safe SHA-1-based filename.

Requirements

Take Quiz

Keep learning

Related questions

View more