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.
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:
bufferholds four byte values:G,o,!, andx.nis3.buffer[:n]creates a slice containing indexes0,1, and2:G,o, and!.string(buffer[:n])creates the string"Go!"from those bytes.fmt.PrintlnprintsGo!.
Now compare a hash:
sum := sha1.Sum([]byte("Go"))
raw := string(sum[:])
encoded := hex.EncodeToString(sum[:])
rawcontains the 20 original digest bytes. It can include non-printable bytes.encodedcontains 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)afteros.ReadFilewhen 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 afterReadreturns the number of bytes read. - Hash-based cache keys: Encode digests with
hex.EncodeToStringfor 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.
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
| Situation | Recommended approach | Why |
|---|---|---|
| Bytes are known UTF-8 text | string(data) | Direct, simple conversion to a Go string |
| Need a section of a byte buffer as text | string(buffer[:n]) | Uses only the bytes that were read |
| Fixed byte array contains text | string(array[:]) | Converts the array's slice to a string |
| Binary hash or arbitrary bytes need text form | hex.EncodeToString(data) | Readable and safe printable representation |
| Binary data needs a shorter text form | base64.StdEncoding.EncodeToString(data) | More compact than hex, but uses additional characters |
| Need to parse JSON bytes | json.Unmarshal(data, &value) | Avoids converting to string unnecessarily |
Hex versus Base64
| Encoding | Output size for 20-byte SHA-1 digest | Typical use |
|---|---|---|
| Hex | 40 characters | Filenames, command-line output, logs, Git-like identifiers |
| Standard Base64 | 28 characters including padding | JSON fields and compact transport |
Hex is often the easier choice for filenames because its character set is limited to digits and lowercase letters.
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]requires0 <= 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.
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
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.