Skip to content

Beginner · Borrowing, slices and lifetimes · topic 16 of 50 · 30 min

Slices and strings

Explanation

A slice is a reference to a part of a collection, like &s[0..5] or &arr[1..3]. String is owned, growable text on the heap; &str is a borrowed view of text, and string literals are &str. Slice ranges count BYTES, and text is stored as UTF-8, so a Hindi letter takes 3 bytes: slicing in the middle of a character panics.

What is a slice?

A slice is a reference to a continuous part of a collection, instead of the whole thing. It is made of two numbers: a pointer to the first item and a length. A slice does not own anything; it borrows.

output
let s = String::from("Hello Delhi");
let hello: &str = &s[0..5];
let delhi: &str = &s[6..];   // from 6 to the end

  s:     ptr ---> H e l l o _ D e l h i
                  ^         ^
  hello: ptr -----+ len 5   |
  delhi: ptr ---------------+ len 5
Both slices point into the same heap bytes owned by s. Nothing is copied.

Range shortcuts: [..3] means from the start, [3..] means to the end, [..] means everything.

String versus &str

String&str (string slice)
Owns its data?Yes, on the heapNo, it borrows
Can grow or change?Yes (push, push_str)No
Made byString::from("x"), "x".to_string(), format!"literal", &my_string, &my_string[0..3]
Use forStoring and building textReading text, function parameters

A string literal like "Mumbai" is a &str that points into the program's own binary file, so it lives for the whole run. Its full type is &'static str, which you will understand in the lifetimes lesson.

Prefer &str for parameters

rust
fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

fn main() {
    let owned = String::from("Jai Hind");
    println!("{}", first_word(&owned));     // works with &String
    println!("{}", first_word("Vande Mataram")); // and with literals
}
A &String is automatically turned into a &str (deref coercion), so a &str parameter accepts both. Prints Jai, then Vande.

UTF-8: why slicing counts bytes

Rust strings are always UTF-8, a way of storing any language's text where each character takes 1 to 4 bytes. English letters take 1 byte. Most Hindi (Devanagari) characters take 3 bytes. The rupee sign ₹ also takes 3 bytes.

rust
let word = "नमस्ते";
println!("{}", word.len());             // 18 bytes
println!("{}", word.chars().count());   // 6 chars
// &word[0..1]  -> panics: byte index 1 is not a char boundary
len() counts bytes. chars() walks over Unicode characters. Here 6 chars x 3 bytes = 18.
Careful: Slicing a string at a byte that is in the middle of a character panics at run time. With user input in Indian languages, never slice with fixed numbers. Use .chars(), .char_indices(), or methods like split, find and trim that always cut at valid boundaries.

Also note: what a reader sees as one letter can be several chars. "स्ते" in नमस्ते is made of several Unicode code points. For "visible letters" you need a crate such as unicode-segmentation; for most programs, working with whole words and chars is enough.

Array slices

rust
fn average(marks: &[u32]) -> f64 {
    let total: u32 = marks.iter().sum();
    total as f64 / marks.len() as f64
}

fn main() {
    let marks = [70, 80, 90, 60];
    println!("{}", average(&marks));        // whole array: 75
    println!("{}", average(&marks[1..3]));  // 80 and 90: 85
    let v = vec![50, 100];
    println!("{}", average(&v));            // a Vec works too: 75
}
&[u32] accepts arrays, Vecs and parts of either.

Common mistakes

  • Writing fn f(s: &String) for parameters. Use &str: it accepts more kinds of input.
  • Indexing a string with s[0]. Rust does not allow it, because a byte may not be a whole character. Use s.chars().next().
  • Assuming len() is the number of letters. It is the number of bytes.
  • Returning a &str slice of a local String from a function. Return the String instead.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Slices and strings · 1/6

&s[0..5] &arr[1..3]

A slice is a reference to part of a collection. It is a pointer plus a length, and it owns nothing.

Prefer a video by a teacher? Find free videos on “rust slices string vs str utf-8 explained” on YouTube ↗

In real life: Showing initials for Indian names

A contacts app shows initials in a circle, like "RK" for Rahul Kumar, and also supports names typed in Hindi. Taking &name[0..1] would panic for "राहुल". The app uses split_whitespace and chars().next(), which always respect character boundaries, so it works for every script.

rust
fn initials(name: &str) -> String {
    name.split_whitespace()
        .filter_map(|w| w.chars().next())
        .collect()
}

fn main() {
    println!("{}", initials("Rahul Kumar"));   // RK
    println!("{}", initials("राहुल कुमार"));   // रक
}
filter_map and collect are iterator tools from module 8; here they gather the first char of each word.

Practice (0/3 done)

Task 1 · Question

What does this print? let s = "Chennai Express"; println!("{}", &s[8..]);

Task 2 · Question

What is "₹500".len()?

Task 3 · Practice

Write fn last_word(s: &str) -> &str that returns the last word of a sentence (use rfind(' ') or split_whitespace().last()). Test it with an English sentence, a Hindi sentence and a single word. Then try &s[0..1] on a Hindi word and read the panic message.

Quick quiz

Question 1

1.What is the type of the literal "Goa"?

Question 2

2.Which function signature accepts both a &String and a literal?

Question 3

3.What does len() return for a String?

Question 4

4.Why can you not write my_string[0] in Rust?

Question 5

5.What does a slice contain?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is the type of the literal "Goa"?

Answer: &str

String literals are &str (more exactly &'static str).

Which function signature accepts both a &String and a literal?

Answer: fn f(s: &str)

&String coerces to &str automatically, and literals already are &str.

What does len() return for a String?

Answer: Number of bytes

len() counts bytes of UTF-8 data.

Why can you not write my_string[0] in Rust?

Answer: A single byte may not be a whole character, so indexing is not allowed

Rust does not let you index strings by position, because bytes are not always characters.

What does a slice contain?

Answer: A pointer and a length

A slice is a fat pointer: where it starts and how many items.

Can you compare two slices with ==?Related

Answer: No, only a slice with nil

Slices are not comparable; use slices.Equal to compare contents.

From Arrays and slices

What does len("India 2024") give?Related

Answer: 10

India has 5 characters, the space is 1 and 2024 is 4 more: 5 + 1 + 4 = 10.

From Strings: working with text

What type do you get from s[0] when s is a string?Related

Answer: byte (uint8)

Indexing a string gives a single byte.

From Strings, bytes and runes

How many bytes does "UPI" occupy as a string literal?Related

Answer: 4

3 letters + the terminator.

From Strings are char arrays

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Borrowing, slices and lifetimes” before its exam: Lifetimes.
  2. Module 4 exam: Borrowing, slices and lifetimes · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Structs, enums and pattern matching. 50 topics left in the course before the final exam.

← Previous: The borrow checker in practice