Vibe Engines
YouTube

Lesson 2.4 · 9 min

Text: String and &str

Text is the one value Chatterbox cannot live without, and Rust has two kinds of it. One is fixed text written in the code. The other is text that can grow. Knowing which is which ends the most common beginner stumble.

Adds to ChatterboxChatterbox holds a message as text.

Two kinds of text

Every quoted piece of text you have written so far, like "Chatterbox", is a &str (say "string slice"). It is fixed: it is written in your program and it never grows or shrinks. The & in front is a hint that you are looking at text that lives somewhere else. Chapter 4 explains that fully; for now just read it as "fixed text".

The other kind is String. This is text your program owns and can change: add to the end, build up piece by piece, hold something typed by a user. Chatterbox needs this for messages, because a message is not known when you write the code.

Turn fixed text into a String, then grow it
fn main() {
let mut message = String::from("Hello");
message.push_str(", world");
message.push('!');
println!("{}", message);
}
$ cargo run
Hello, world!

String::from("Hello") copies fixed text into a new String. push_str adds more text on the end, and push adds one character. Because the text changes, the name needs let mut, exactly as in lesson 2.2.

The compiler keeps them apart

The two kinds are different types, so one cannot quietly stand in for the other.

Fixed text is not a String
fn main() {
let greeting: String = "Hello";
println!("{}", greeting);
}
error[E0308]: mismatched types
 --> src/main.rs:2:28
  |
2 |     let greeting: String = "Hello";
  |                   ------   ^^^^^^^ expected `String`, found `&str`
  |                   |
  |                   expected due to this
  |
help: try using a conversion method
  |
2 |     let greeting: String = "Hello".to_string();
  |                                   ++++++++++++

"mismatched types": the name was promised to be a String but the value is a &str. The help text suggests .to_string(). Both .to_string() and String::from do the same job: make an owned String from fixed text.

The other direction is free. Anywhere a function wants fixed text, you can hand it a String by writing & in front. That is why the helper functions in this course take &str: they accept both.

Combining text

You have met format!. It builds a new String from pieces and leaves the originals alone, so it is usually the cleanest way to join text.

PredictHow long is it?
fn main() {
let word = "héllo";
println!("{}", word.len());
}

What does this print? Remember that len() counts bytes, not letters.

  • 5
  • 6
  • 4
Reveal the answer

The program prints: 6 The letter e with an accent takes two bytes in UTF-8, the encoding Rust uses for text, so five letters are six bytes. len() reports bytes. Counting letters is a different method, chars().count(), and you will not need it yet.

Chatterbox v0.2, finished

Time to finish version 0.2. The banner and the greeting are now built as values with format!, the greeting grows with push_str, and everything is printed together at the end. Read it top to bottom: names first, then the text, then the output.

src/main.rs: Chatterbox v0.2
fn main() {
let name = "Sam";
let bot = "Chatterbox";
let version = "0.2";
let mut message_count = 0;
let token_budget: u32 = 4096;
let quiet = false;
 
let banner = format!("{} v{}", bot, version);
let mut greeting = format!("Hi {}! I'm {}.", name, bot);
greeting.push_str(" Ask me anything.");
message_count += 1;
println!("{}", banner);
println!("{}", greeting);
println!("{} messages so far", message_count);
println!("Budget: {} tokens, quiet: {}", token_budget, quiet);
}
$ cargo run
Chatterbox v0.2
Hi Sam! I'm Chatterbox. Ask me anything.
1 messages so far
Budget: 4096 tokens, quiet: false
Try itYour turn: label a message

Chatterbox shows who said what. Make labelled return You: followed by the message. Start from the String that is already there and grow it. You don't need to understand fn yet (chapter 3, lesson 4 explains it): just fill in the body.

Chatterbox shows who said what. Make labelled return You: followed by the message. Start from the String that is already there and grow it. You don't need to understand fn yet (chapter 3, lesson 4 explains it): just fill in the body.

fn labelled(message: &str) -> String {
let mut line = String::from("You: ");
// add the message to the end of line
line
}
 
fn main() {
println!("{}", labelled("hello"));
}

Hidden tests (3): empty_message labels_a_message different_messages_differ

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

What you have now

Chapter 2 is done. Chatterbox v0.2 has names, a counter, numbers, a switch and growing text. It still does the same thing every time you run it. In chapter 3 it starts to decide.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
What is the difference between a &str and a String?Reveal
A &str is fixed text written in the code. A String is text the program owns and can grow.
How do you turn fixed text into a String?Reveal
String::from("text") or "text".to_string().
What does len() count for text?Reveal
Bytes, not letters. An accented letter is more than one byte.

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

Next: 3.1 Choosing with if →

Questions people ask

When should I use String and when &str?

Use &str for text you only read, such as function inputs. Use String when you need to own, build or change text.

Why does Rust have two string types?

One is fixed and cheap to pass around; the other owns its characters and can grow. Keeping them as separate types makes it clear which you are holding.

What does the & in &str mean?

It marks a reference: the text is borrowed from somewhere else. Chapter 4 explains borrowing.

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