ProgramGuru

String, str and Unicode text

String, str and Unicode text

Student names do not always fit one byte per letter. Rust stores String and str text as UTF-8. String owns a growable buffer; str is a text slice type, commonly accessed through &str. Understanding that distinction prevents incorrect indexing and unnecessary ownership transfers.

What you’ll learn

  • Choose String ownership or an &str text view.
  • Distinguish UTF-8 bytes, Unicode scalars and grapheme clusters.

String, str and Unicode text in action

  1. Zoë: UTF-8 text

    Z and o each use one byte, while ë uses two in UTF-8.

    Zoë: UTF-8 text
  2. 4 bytes versus 3 scalars

    The text therefore has four bytes and three scalar values.

    4 bytes versus 3 scalars
  3. Append class label

    Appending the class label grows the owned String.

    Append class label
Z and o each use one byte, while ë uses two in UTF-8. The text therefore has four bytes and three scalar values. Appending the class label grows the owned String. The original accent remains valid text.

String, str and Unicode text explained

Use push_str to append borrowed text to a mutable String. len() counts bytes and chars() yields Unicode scalar values. Rust does not provide direct integer indexing for String because a byte position does not consistently identify a character. User-perceived grapheme clusters can contain multiple scalar values.

Connect this idea with references and read-only borrowing and slices and reference lifetimes.

Work through the Rust program

Save the complete source as main.rs in the Rust environment. No standard input is needed.

Rust 2024 · complete program

fn main() {
  let mut name = String::from("Zoë");
  println!("Bytes: {}", name.len());
  println!("Scalars: {}", name.chars().count());
  name.push_str(" - A");
  println!("{name}");
}

Expected output

Bytes: 4
Scalars: 3
Zoë - A

Follow the execution

Z and o each use one byte, while ë uses two in UTF-8. The text therefore has four bytes and three scalar values. Appending the class label grows the owned String. The original accent remains valid text.

Try the complete program in the Rust compiler. For tests, Cargo commands and local files, use the specified local environment.

Try a small variation

Your task: Is name.len() a reliable count of letters shown to a reader?

Rust 2024 · complete program

fn main() {
  let mut name = String::from("Zoë");
  println!("Bytes: {}", name.len());
  println!("Scalars: {}", name.chars().count());
  name.push_str(" - A");
  println!("{name}");
}

Worked answer

No. It counts UTF-8 bytes. Even chars().count() counts scalar values rather than every user-perceived grapheme cluster.

Bytes, scalar values and displayed letters

Slicing a string with byte positions can panic if a boundary splits a UTF-8 encoding. Grapheme processing needs a suitable library. Prefer &str for a function that only reads text and String when the function needs owned, growable text.

API details: official Rust documentation for this boundary.

Check your understanding

Is name.len() a reliable count of letters shown to a reader?
Choose one answer. You can try again.

Answer and explanation

No.

No. It counts UTF-8 bytes. Even chars().count() counts scalar values rather than every user-perceived grapheme cluster.

Take the idea into your next program

No. It counts UTF-8 bytes. Even chars().count() counts scalar values rather than every user-perceived grapheme cluster. Slicing a string with byte positions can panic if a boundary splits a UTF-8 encoding.

Further reading: the official Rust Book on string, str and unicode text.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Vectors and changing collections →