Skip to content

Rust String Length: Bytes, chars and UTF-8 Slice Boundaries

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.

ExpressionExpected resultReason
text.get(..1)NoneInside é
text.get(..2)Some("é")After é
text.get(..3)NoneInside 😀
text.get(..6)Some("é😀")End of text
text.get(..9)NoneOut 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.