Rust String Length: Bytes, chars and UTF-8 Slice Boundaries
Published · Updated
Rust string length depends on the unit you need. str::len() counts UTF-8 bytes; chars().count() counts Unicode scalar values. A string-slice range also uses byte offsets, so a number obtained by counting scalar values is not automatically a valid slicing index.
Separate bytes from Rust char values
The Rust Book explains that both String and str store UTF-8 text. For the exact text é😀, the precomposed accented letter uses two bytes and the emoji uses four: six bytes contain two scalar values.
A Rust char is a Unicode scalar value. It is not a general measure of visible letters or emoji. A base letter and a combining mark can be separate scalar values in one user-perceived character; changing the counting method does not change the underlying text.
Check the byte range before slicing
The str reference documents get() as the non-panicking alternative to indexing. It returns None when the equivalent range indexing would panic, including an endpoint inside a UTF-8 sequence or beyond the string.
These expected prefix results follow from that method contract and the exact UTF-8 bytes of é😀. The arithmetic was checked separately; this guide does not claim a Rust compiler test.
| Expression | Expected result | Reason |
|---|---|---|
text.get(..1) | None | Inside é |
text.get(..2) | Some("é") | After é |
text.get(..3) | None | Inside 😀 |
text.get(..6) | Some("é😀") | End of text |
text.get(..9) | None | Out of bounds |
Use this read-only example to compare the operations in your Rust environment. Its expected output is 6 2, then None, then Some("é").
fn main() {
let text = "é😀";
println!("{} {}", text.len(), text.chars().count());
println!("{:?}", text.get(..1));
println!("{:?}", text.get(..2));
}
Handle None explicitly. The method does not round the requested endpoint down or return the longest shorter prefix. Decide whether to reject that range or choose a different boundary according to the receiving system's rule.
A valid UTF-8 boundary can still split a visible character
For e\u{301}, meaning a base e followed by a combining acute accent, the text has three UTF-8 bytes and two scalar values. Byte offset one is a valid boundary, so its prefix contains just the base letter. It leaves the combining mark behind.
Unicode grapheme segmentation addresses user-perceived character boundaries. A check that preserves valid UTF-8 does not by itself preserve a whole grapheme cluster. If a displayed-character limit is involved, identify that rule before selecting a prefix.
The String Length guide compares counting units. For a separate JavaScript implementation that keeps complete grapheme clusters within a byte budget, see the JavaScript UTF-8 byte-limit guide; its code is not a Rust implementation.