Question

Counting Characters in Rust Strings: bytes vs chars

ruststring

Question

In Rust, String::len() returns the number of bytes in the string, not the number of characters. This can be confusing when working with Unicode text.

For example:

let s = String::from("ラウトは難しいです!");
println!("{}", s.len()); // returns 30

The string contains 10 Unicode scalar values, but len() returns 30 because the text is encoded as UTF-8 and each character uses multiple bytes.

I found that this gives the character count I expect:

let count = s.chars().count();
println!("{}", count); // returns 10

Is there any built-in method on String that returns the number of characters directly, other than using s.chars().count()?

Short Answer

By the end of this page, you will understand why Rust strings report their length in bytes, how to count Unicode scalar values with chars().count(), and when that still differs from what users think of as a “character”. You will also learn the practical trade-offs between byte length, char count, and grapheme cluster count.

Concept

Rust String values are stored as UTF-8. UTF-8 is a variable-width encoding, which means:

  • Some characters use 1 byte.
  • Others use 2, 3, or 4 bytes.

Because of this, Rust's String::len() returns the number of bytes, not the number of human-readable characters.

let s = "hello";
println!("{}", s.len()); // 5 bytes

let s = "ラウト";
println!("{}", s.len()); // more than 3 bytes

In Rust, there are three different ideas that beginners often mix up:

  • Bytes: the raw UTF-8 data stored in memory
  • char values: Unicode scalar values
  • Grapheme clusters: what a user usually sees as one character on screen

For many tasks, s.chars().count() is the correct answer if you want the number of Unicode scalar values. There is no special built-in String method like char_len() that does this directly. The standard Rust approach is:

s.chars().count()

Why Rust does this:

  • Byte length is fast to retrieve.
  • Random indexing by character is not safe or efficient in UTF-8.
  • Rust makes Unicode handling explicit, so you choose the correct unit for your task.

This matters in real programs because text processing can mean very different things depending on the goal:

  • Storage size -> use bytes
  • Unicode scalar count -> use chars()
  • What a user sees as one visible character -> use grapheme clusters from a crate such as unicode-segmentation

So the short answer is: no, there is no separate built-in String method for character count; s.chars().count() is the normal standard-library solution.

Mental Model

Think of a Rust string like a sentence stored in boxes of varying sizes.

  • len() asks: how many boxes are being used?
  • chars().count() asks: how many Unicode symbols are stored?
  • Grapheme counting asks: how many symbols does a person visually see?

These are not always the same.

For example, one visible symbol on screen can be made of:

  • one byte
  • several bytes
  • one char
  • multiple char values combined

So when working with strings in Rust, the first question is not “How long is this string?” but rather:

Long in what unit?

Take Quiz

Syntax and Examples

The most common string length-related operations in Rust are:

let s = String::from("こんにちは");

println!("bytes: {}", s.len());
println!("chars: {}", s.chars().count());

Example 1: ASCII text

let s = String::from("hello");
println!("bytes: {}", s.len());
println!("chars: {}", s.chars().count());

Output:

bytes: 5
chars: 5

For plain ASCII, bytes and chars are the same.

Example 2: Japanese text

let s = String::from("ラウトは難しいです!");
println!("bytes: {}", s.len());
println!("chars: {}", s.chars().count());

Output:

bytes: 30
chars: 10

Each Japanese character uses multiple bytes in UTF-8, so byte count is larger.

Example 3: Visible characters can still differ

let s = "e\u{301}"; // e + combining accent
println!("bytes: {}", s.len());
println!("chars: {}", s.chars().count());

This may display as a single visible character, but it contains multiple Unicode parts.

If you need user-visible characters

Rust's standard library does not count grapheme clusters directly. For that, developers often use unicode-segmentation:

use unicode_segmentation::UnicodeSegmentation;

let s = "e\u{301}";
println!("graphemes: {}", s.graphemes(true).count());

Use this only when you specifically need what users perceive as characters.

Step by Step Execution

Consider this example:

let s = String::from("aé日");
println!("bytes = {}", s.len());
println!("chars = {}", s.chars().count());

Let's trace it.

Step 1: Create the string

let s = String::from("aé日");

This string contains:

  • a
  • é
  • 日

Step 2: Count bytes with len()

s.len()

In UTF-8:

  • a uses 1 byte
  • é uses 2 bytes
  • 日 uses 3 bytes

Total:

1 + 2 + 3 = 6 bytes

So:

println!("bytes = {}", s.len()); // 6

Step 3: Count Unicode scalar values with chars().count()

s.chars().count()

chars() iterates over each Unicode scalar value in the string:

  1. a
  2. é
  3. 日

That gives:

3 chars

So:

println!("chars = {}", s.chars().count()); // 3

Important detail

len() is fast because the string already stores its byte length.

chars().count() must walk through the string and decode UTF-8, so it is not a constant-time lookup.

Real World Use Cases

Measuring storage or network size

If you are checking payload size, file size, database limits, or protocol limits, use:

s.len()

This tells you how many bytes the string uses.

Validating a maximum number of Unicode characters

If a rule says “maximum 50 Unicode characters”, use:

s.chars().count()

Example:

if username.chars().count() > 50 {
    println!("Username is too long");
}

Limiting what users see on screen

For UI text, emojis, accented characters, and combined symbols, visible length may differ from chars().count(). In that case, grapheme clusters are more appropriate.

Processing multilingual text

When parsing international input, avoid assuming one byte equals one character. Rust's string model helps prevent those mistakes.

Safe iteration over text

Instead of indexing strings directly, Rust developers commonly use:

  • chars() for Unicode scalar values
  • bytes() for raw bytes
  • grapheme iteration for user-facing text
Take Quiz

Real Codebase Usage

In real Rust projects, developers usually choose string length logic based on the requirement rather than looking for one universal "length" method.

Common patterns

Validation

fn is_valid_name(name: &str) -> bool {
    let count = name.chars().count();
    count >= 2 && count <= 30
}

This is common in forms, APIs, and CLI tools.

Byte-size guards

fn fits_in_header(value: &str) -> bool {
    value.len() <= 256
}

Useful when protocols or storage systems define byte limits.

Early returns

fn validate_message(msg: &str) -> Result<(), String> {
    if msg.is_empty() {
        return Err("message cannot be empty".to_string());
    }

    if msg.chars().count() > 140 {
        return Err("message too long".to_string());
    }

    Ok(())
}

Explicit Unicode handling

In production code, developers often document what "length" means:

  • bytes
  • Unicode scalar values
  • grapheme clusters

This avoids subtle bugs and confusing validation rules.

Avoiding repeated counts

Because chars().count() walks the string, developers may compute it once and reuse it:

let char_count = s.chars().count();
if char_count > 20 {
    println!("too long");
}

Common Mistakes

Mistake 1: Assuming len() means character count

Broken expectation:

let s = "こんにちは";
assert_eq!(s.len(), 5); // wrong

Why it fails:

  • len() returns bytes, not Unicode scalar values.

Correct approach:

assert_eq!(s.chars().count(), 5);

Mistake 2: Assuming chars().count() is user-visible length

let s = "e\u{301}";
println!("{}", s.chars().count());

This may print 2, even though the user may see one character.

Avoid it by clarifying your requirement:

  • Need Unicode scalar values? Use chars().count()
  • Need visible characters? Use grapheme clusters

Mistake 3: Trying to index strings by character position

Broken code:

let s = String::from("こんにちは");
let first = s[0];

Rust does not allow this because character boundaries in UTF-8 are not fixed-width.

Use iteration instead:

let first = s.chars().next();

Mistake 4: Recounting repeatedly in a loop

for _ in 0..1000 {
    if s.chars().count() > 10 {
        println!("long");
    }
}

This needlessly recomputes the count many times.

Better:

let count = s.chars().count();
for _ in 0..1000 {
    if count > 10 {
        println!("long");
    }
}

Mistake 5: Using byte slicing carelessly

Broken code:

let s = "ラスト";
let part = &s[0..2];

This can panic if the slice does not align with UTF-8 character boundaries.

Prefer safer approaches such as iterating with chars() when working at character level.

Comparisons

TaskRecommended Rust approachWhat it measuresNotes
Get raw storage lengths.len()BytesFast and built-in
Count Unicode scalar valuess.chars().count()char valuesStandard-library solution
Count user-visible characterss.graphemes(true).count()Grapheme clustersRequires unicode-segmentation
Iterate over raw encoded datas.bytes()BytesUseful for low-level work
Iterate over Unicode scalar valuess.chars()char valuesCommon for text logic

len() vs chars().count()

MethodReturnsConstant time?Built in?
len()Number of bytesYesYes
chars().count()Number of Unicode scalar valuesNoYes

chars() vs graphemes

ConceptExample concernBest choice
Unicode scalar valuesParsing text logicallychars()
What the user sees as one symbolUI counters, cursor movementGraphemes
Take Quiz

Cheat Sheet

// Bytes in UTF-8 string
s.len()

// Unicode scalar value count
s.chars().count()

// First Unicode scalar value
s.chars().next()

Rules to remember

  • String::len() returns bytes, not characters.
  • Rust strings are UTF-8.
  • A char in Rust is a Unicode scalar value.
  • One visible character on screen may be made of multiple char values.
  • There is no built-in String method like char_len().
  • The standard way to count scalar values is:
s.chars().count()

Edge cases

  • ASCII text: byte count and char count are often the same.
  • Non-ASCII text: byte count is often larger.
  • Combined Unicode symbols: visible count may differ from chars().count().

Performance note

  • len() is cheap.
  • chars().count() scans the string.

If you need visible characters

Use the unicode-segmentation crate for grapheme clusters.

FAQ

Does Rust have a built-in method to count characters in a String?

Not as a dedicated String method. The standard approach is s.chars().count().

Why does String::len() return bytes instead of characters?

Because Rust strings are stored as UTF-8, and byte length is the direct size of the underlying data.

Is chars().count() the same as the number of visible characters?

Not always. It counts Unicode scalar values, which can differ from what users see on screen.

What should I use for user-visible character counts in Rust?

Use grapheme cluster counting, typically with the unicode-segmentation crate.

Is chars().count() slower than len()?

Yes. len() is constant time, while chars().count() must iterate through the string.

Why can't I index a Rust string like s[0]?

Because UTF-8 characters have variable byte lengths, so direct character indexing would be ambiguous and unsafe.

Should I use String or &str for counting characters?

Either works. Methods like len() and chars() are commonly used on &str, and String can be borrowed as &str.

Take Quiz

Mini Project

Description

Build a small Rust text analyzer that reports three different kinds of string length: bytes, Unicode scalar values, and whether the string is empty. This project helps reinforce that “string length” depends on what exactly you are measuring.

Goal

Create a program that accepts a few example strings and prints their byte length and character count correctly.

Requirements

  • Create a Rust program with at least three example strings, including ASCII and non-ASCII text.
  • Print the original string for each example.
  • Print the result of both len() and chars().count().
  • Add a simple validation message if the string has more than 10 Unicode scalar values.
Take Quiz

Keep learning

Related questions

View more