Vibe Engines
YouTube

Lesson 6.1 · 8 min

A list: Vec

So far Chatterbox forgets every message the moment it handles it. To keep a conversation you need a list that grows. Rust's growable list is called Vec, and it follows the same one-owner rule you already know.

Adds to ChatterboxThe conversation history.

A list that grows

A Vec (said "vec", short for vector) is a list of values that all have the same type, and it can grow. You write the type as Vec<Message>: a Vec of Messages. The part in angle brackets says what is inside.

Think of it with the one-owner rule from chapter 4. The Vec owns every item in it. When the Vec goes away, its items go with it. Nobody else holds a copy.

Make a list, add to it, count it
fn main() {
let mut names: Vec<String> = Vec::new();
names.push("sam".to_string());
names.push("ada".to_string());
println!("{} people", names.len());
println!("first is {}", names[0]);
}
$ cargo run
2 people
first is sam

Vec::new() makes an empty list. When you already know the items, vec!["sam".to_string(), "ada".to_string()] makes a list with them in it: vec! is a macro (like println!) that builds a Vec from the items you list. push adds one item at the end, so the Vec has to be mut (you met mut in chapter 2). len() says how many items it holds. (Its answer, and any position you pass, has the type usize: a number that means "a position or a size".) names[0] reads the item at position 0: counting starts at zero, so the first item is number 0. In the next program, .clone() makes a second, separate copy of a String, so the function can hand one back while the list keeps its own.

What if the position is not there?

There are only two names in that list. What happens if you ask for position 5?

PredictPast the end
fn pick(names: &Vec<String>, i: usize) -> String {
names[i].clone()
}
 
fn main() {
let names = vec!["sam".to_string(), "ada".to_string()];
println!("{}", pick(&names, 5));
}

This program asks for position 5 of a two-item list. What happens?

  • It compiles and runs
  • It compiles, then panics
  • Compile error E0308
Reveal the answer

It compiles, then panics at run time. The program compiles, because the compiler cannot know how long the list is at run time. When it runs, position 5 does not exist, so the program stops with a panic: "index out of bounds". Reading by [ ] is a promise that the item is there. If you break the promise, the program crashes.

The polite way: get

A chat program receives messages from other people, so "position 5 does not exist" is a normal thing to meet, not a bug. For those cases a Vec has get. It does not crash. It returns an Option (lesson 5.4): Some(item) when the position exists and None when it does not.

Use [ ] when you have just checked the length or the position is certain. Use get whenever it might be missing.

get hands back an Option
fn main() {
let names = vec!["sam".to_string(), "ada".to_string()];
println!("{:?}", names.get(1));
println!("{:?}", names.get(5));
}
$ cargo run
Some("ada")
None

Chatterbox v0.6: it remembers

Now give Chatterbox a history. Make an empty Vec<Message> before the loop. Every time someone says something, build a Message and push it. The Vec takes ownership of the message, so there is nothing else to tidy up. At the end, report how many messages it holds and use get(0) to look at the first one, because there might not be one.

The highlighted lines are the new ones. For now every message comes from the same user, "sam". Several users come two lessons from now.

src/main.rs: Chatterbox v0.6
struct Message {
author: String,
text: String,
}
 
impl Message {
fn new(author: &str, text: &str) -> Message {
Message { author: author.to_string(), text: text.to_string() }
}
 
fn shout(&self) -> String {
self.text.to_uppercase()
}
 
fn is_empty(&self) -> bool {
self.text.is_empty()
}
}
 
enum Command {
Help,
Quit,
Say(String),
Unknown(String),
}
 
fn parse(line: &str) -> Command {
match line {
"/help" => Command::Help,
"/quit" => Command::Quit,
_ => match line.strip_prefix("/say ") {
Some(rest) => Command::Say(rest.to_string()),
None => Command::Unknown(line.to_string()),
},
}
}
 
fn main() {
let user = "sam";
let mut history: Vec<Message> = Vec::new();
let lines = ["/help", "/say hello there", "/say anyone home?", "/bogus", "/quit"];
for line in lines {
match parse(line) {
Command::Help => println!("commands: /help /say /quit"),
Command::Quit => break,
Command::Say(text) => {
history.push(Message::new(user, &text));
println!("saved message {}", history.len());
}
Command::Unknown(other) => println!("unknown command: {other}"),
}
}
println!("history has {} messages", history.len());
match history.get(0) {
Some(first) => println!("first: {}", first.text),
None => println!("nothing said yet"),
}
}
$ cargo run
commands: /help /say /quit
saved message 1
saved message 2
unknown command: /bogus
history has 2 messages
first: hello there
Try itYour turn: the last thing said

Write last_text. It returns Some(text) of the newest message (the one at the end of the Vec), or None if the history is empty. It must never crash.

Write last_text. It returns Some(text) of the newest message (the one at the end of the Vec), or None if the history is empty. It must never crash.

struct Message {
author: String,
text: String,
}
 
impl Message {
fn new(author: &str, text: &str) -> Message {
Message { author: author.to_string(), text: text.to_string() }
}
}
 
fn last_text(history: &Vec<Message>) -> Option<String> {
// newest message = the last position in the list
None
}
 
fn main() {
let mut history: Vec<Message> = Vec::new();
println!("{:?}", last_text(&history));
history.push(Message::new("sam", "hello"));
println!("{:?}", last_text(&history));
}

Hidden tests (3): empty_history_has_no_last_text newest_is_the_last_one_pushed single_message

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

What you have now

Chatterbox keeps a Vec<Message>. You can push, count, index and get. The list owns its messages, and a missing position is something you handle, not something you hope never happens. Next you will walk through the list instead of picking one position at a time.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
What is the difference between names[5] and names.get(5) on a two-item Vec?Reveal
names[5] panics (the program crashes). names.get(5) returns None, so you can handle it.
Why does the Vec need to be mut before you push?Reveal
push changes the list, and Rust makes you say a value may change by writing mut.
Who owns the Messages you push?Reveal
The Vec. After the push the message has moved in, and the Vec drops it when the Vec itself goes away.

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

Next: 6.2 Walking a list →

Questions people ask

What does Vec stand for in Rust?

Vec is short for vector: a growable list of values that all have the same type.

Is a Rust Vec the same as a Python list?

It is the closest match, but every item must be the same type, and reading past the end panics instead of raising an exception you can catch in the usual way.

How do I make a Vec with items already in it?

Use the vec! macro, for example vec![1, 2, 3].

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