Vibe Engines
YouTube

Lesson 4.5 · 11 min

Parts of a value

Chatterbox should understand "/say hello" as a command and its text. A slice is a borrowed window onto part of a String, and it follows every rule you have already learned.

Adds to ChatterboxPrint the first word of a message.

A window onto part of a value

Chatterbox gets commands like /say hello. To react, it must treat /say as the command and hello as the text to say. You could build two new Strings, but each would copy text that is already there. Rust has a better idea: a slice is a borrowed window onto part of the text.

You write it with &, then the value, then a range in square brackets. Counting starts at 0 and the end is not included, so [..4] is "from the start up to position 4".

Two slices of one String, and no copying
fn main() {
let msg = String::from("/say hello");
let cmd = &msg[..4];
let rest = &msg[5..];
println!("{cmd}|{rest}");
}
$ cargo run
/say|hello
PredictWhich part is it?
fn main() {
let s = String::from("hello world");
println!("{}", &s[0..5]);
}

What does this print?

  • hello
  • hello
  • ello w
Reveal the answer

The program prints: hello The range 0..5 takes positions 0 up to but not including 5: h, e, l, l, o. The space is at position 5, so it stays out.

Watch what a slice is made of. It is a loan: it points into the owner's text and says "from here, this many bytes". The same text has one owner and two borrowers, and nothing was copied.

Ownership Stagecmd and rest are borrowed windows onto msg

Step through the program. Solid arrows are ownership; dashed arrows are borrows. Use ← → or the slider.

fn main() {
let msg = String::from("/say hello");
let cmd = &msg[..4];
let rest = &msg[5..];
println!("{cmd}|{rest}");
}
  1. Line 1. The program starts in main.
  2. Line 2. msg owns ten bytes of text: the slash, three letters, a space and five letters.
  3. Line 3. cmd is a slice of the first four bytes, /say. It is a borrow: it points at part of the text and owns nothing. cmd: &str borrows bytes 0..4 of msg.
  4. Line 4. rest is a slice from position 5 to the end, hello. Position 4 is the space, which neither slice holds. rest: &str borrows bytes 5..10 of msg.
  5. Line 5. Both are read here, which is their last use, so both loans end. This prints /say|hello. stdout: /say|hello
  6. Line 6. main ends and msg's text is cleaned up once.

A slice is a borrow, so the old rules hold

That sounds like a footnote, and it is the most useful consequence in the lesson. Because cmd is a loan, the owner may not change msg while cmd is still going to be used. If it did, the window could point at text that has been rewritten or removed. Here is the same mistake in lesson 4.3 shape, with a function returning the slice.

A slice keeps the loan alive. clear() wants to change the owner.
fn first_word(text: &str) -> &str {
match text.find(' ') {
Some(i) => &text[..i],
None => text,
}
}
 
fn main() {
let mut msg = String::from("/say hello");
let cmd = first_word(&msg);
msg.clear();
println!("{cmd}");
}
error[E0502]: cannot borrow `msg` as mutable because it is also borrowed as immutable
  --> src/main.rs:11:5
   |
10 |     let cmd = first_word(&msg);
   |                          ---- immutable borrow occurs here
11 |     msg.clear();
   |     ^^^^^^^^^^^ mutable borrow occurs here
12 |     println!("{cmd}");
   |                --- immutable borrow later used here

In the code above, find gives back Some(position) if it finds the space and None if not.

Read the signature: fn first_word(text: &str) -> &str. There is one borrowed input and one borrowed output. Rust reads that as "the result is a window into the input", which is exactly what the code does, so the output is a loan of msg too. You do not have to write anything more for that. (The Rust Book calls this lifetimes, in its chapter 10; ignore the word for now.)

Finishing with cmd before clear is the fix, as in 4.3. The message, from the owner's point of view, is read-only while a window onto it is still in use.

Chatterbox v0.4

Add two helpers that split a message at its first space, then let reply react to a command with the right text. The new lines are marked. At the end of this chapter Chatterbox keeps a transcript and reads /command text without copying a byte.

src/main.rs: commands split with slices
fn first_word(text: &str) -> &str {
match text.find(' ') {
Some(i) => &text[..i],
None => text,
}
}
 
fn rest_of(text: &str) -> &str {
match text.find(' ') {
Some(i) => &text[i + 1..],
None => "",
}
}
 
fn reply(msg: &str) -> String {
match first_word(msg) {
"/help" => String::from("commands: /help, /say <text>, /quit"),
"/quit" => String::from("bye"),
"/say" => String::from(rest_of(msg)),
_ => format!("echo: {msg}"),
}
}
 
fn record(log: &mut String, who: &str, msg: &str) {
log.push_str(who);
log.push_str(": ");
log.push_str(msg);
log.push('\n');
}
 
fn main() {
let mut transcript = String::new();
let msg = String::from("/say hello there");
let answer = reply(&msg);
record(&mut transcript, "you", &msg);
record(&mut transcript, "bot", &answer);
print!("{transcript}");
}
$ cargo run
you: /say hello there
bot: hello there
Try itYour turn: the last word

Write last_word, which returns a slice of the text after the last space (text has a method rfind(" ") that works like find but looks from the end), or the whole text when there is no space. It must not allocate a new String.

Write last_word, which returns a slice of the text after the last space (text has a method rfind(" ") that works like find but looks from the end), or the whole text when there is no space. It must not allocate a new String.

fn last_word(text: &str) -> &str {
// the part after the last space; the whole text when there is none
text
}
 
fn main() {
println!("{}", last_word("tell me more"));
}

Hidden tests (4): takes_the_final_word two_words single_word_is_itself works_on_a_string

This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
What does &msg[0..4] give you?Reveal
A borrowed slice of the first four bytes of msg. Nothing is copied and msg keeps its ownership.
Why can you not clear a String while a slice of it is still used?Reveal
The slice is a borrow. Changing the owner while the loan is live is refused with E0502.
What does a function signature fn first_word(text: &str) -> &str promise?Reveal
That the result is a window into the input, so the caller must keep the input alive and unchanged while it uses the result.

That is lesson 4.5. Tick it off so the course remembers where you are.

Learn Rust by building a chat app · Lesson 4.5 · every compiler message above is real output from rustc 1.99.0 (b940084d7 2026-09-28) (edition 2024) · Vibe Engines · 2026