Go vs. Rust: Why String Indexing Is Tricky

Rust prevents direct string indexing to force correctness, while Go treats strings as raw byte slices. This matters for non-ASCII text where characters span multiple bytes. The footgun: Go's s[i] can corrupt data; Rust's &s[..i] can panic.
Why it exists
The term "character" is ambiguous. A string is stored as a sequence of bytes, but a single visual character (like 'é' or 'न') can be one or more bytes. Languages must provide a way to access string data that is both efficient and safe, forcing a tradeoff between convenience and correctness.
The mental model
Think of a UTF-8 string as a byte array. Go gives you direct, unchecked access to this array, like a key to a room full of parts. Rust acts as a security guard, asking for your intent before letting you in: "Do you want the raw bytes, or do you want the assembled Unicode characters?" This prevents you from accidentally grabbing half a character.
How it works
Go's approach prioritizes simplicity. Indexing a string, s[i], accesses the i-th byte. A standard for loop iterates byte by byte. For character-aware iteration, Go provides a special for...range loop that decodes one UTF-8 rune (a code point) at a time. Slicing a Go string is a byte-level operation and can create invalid UTF-8 if a slice boundary splits a multi-byte character.
Rust's approach prioritizes safety. Direct indexing is a compile-time error. To iterate, you must be explicit: s.bytes() for bytes or s.chars() for Unicode scalar values (code points). Slicing is allowed (&s[..i]), but it will cause the program to panic at runtime if the slice boundary is not on a valid character boundary. For true visual characters (grapheme clusters), you need an external crate like unicode-segmentation.
When to use it
In Go, use the for...range loop for almost all string iteration. Use byte indexing only when you are certain you are dealing with raw bytes or pure ASCII.
In Rust, use .chars() for iterating over Unicode code points and .bytes() when interacting with byte-level protocols. Slicing is safe if the indices are derived from string search functions, which guarantee valid boundaries.
When not to use it
Never use simple index-based loops or slicing (s[i], s[i:j]) on Go strings that may contain multi-byte characters. You risk corrupting data or getting nonsensical results.
In Rust, avoid slicing with hardcoded or calculated integer offsets unless you have validated them. This is a common source of panics.
One canonical example
For the string s = "नमस्ते":
In Go, s[0] returns 224, the first byte of the character 'न'. A for...range loop correctly yields the runes for 'न', 'म', 'स', etc.
In Rust, attempting s[0] fails to compile. s.chars() correctly iterates over the code points. Slicing with &s[..2] panics because the byte at index 2 is in the middle of a character.
Interview question
Which statement accurately describes a key difference in how Go and Rust handle string indexing with multi-byte characters?
- a.Rust's direct indexing (&s[..i]) is compile-time safe, whereas Go's (s[i]) can panic at runtime.
- b.Go prevents direct indexing to ensure character integrity, while Rust allows it but panics on invalid boundaries.
- c.Both languages use for...range loops as the primary safe method for character iteration.
- d.Go's s[i] accesses individual bytes, potentially corrupting multi-byte characters, while Rust's direct s[i] is a compile-time error.Correct
Why? this is the answer
Go's s[i] provides byte-level access, which can lead to data corruption when dealing with multi-byte characters. In contrast, Rust prevents direct indexing (s[i]) at compile time to enforce character boundary safety, requiring explicit methods like .chars() or careful slicing.
Just read this? Test yourself on what you have been reading.
Read the original → magodo.github.io
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on go — each one lists the topics its interview covers.
See open roles