Question
Rust does not appear to provide std::queue or std::stack adapters like C++. When a program needs a FIFO queue or a LIFO stack with operations such as push, pop, front, and back, which Rust collections should be used?
Short Answer
Rust usually uses general-purpose collections rather than separate Stack and Queue types. Use Vec<T> as a LIFO stack and VecDeque<T> as an efficient FIFO queue. You will learn the relevant methods, why the collections differ, and how to choose the right one.
Concept
A stack and a queue describe how values are accessed:
- A stack is LIFO: last in, first out. The most recently added item is removed first.
- A queue is FIFO: first in, first out. The earliest added item is removed first.
Rust's standard library does not provide direct equivalents of C++'s std::stack and std::queue container adapters. Instead, it provides collections with operations that naturally implement these behaviors:
- Use
Vec<T>for a stack. - Use
std::collections::VecDeque<T>for a queue.
This choice is important for performance. A Vec is optimized for adding and removing values at its end, so it is ideal for a stack. Removing from the beginning of a Vec shifts all remaining elements, which is inefficient for a queue. VecDeque supports efficient operations at both the front and back, making it appropriate for FIFO queues.
Mental Model
Think of a stack as a pile of plates:
- Put a plate on the top.
- Take a plate from the top.
- The last plate placed there is the first one removed.
Think of a queue as a line at a checkout:
- New customers join at the back.
- The customer at the front is served first.
- The first customer to arrive is the first customer to leave.
Vec is built for working efficiently at one end, like the top of the plate pile. VecDeque is built for working efficiently at both ends, like a line with an entrance and an exit.
Syntax and Examples
For a stack, use Vec<T> and treat its end as the top:
let mut stack: Vec<&str> = Vec::new();
stack.push("first");
stack.push("second");
assert_eq!(stack.last(), Some(&"second"));
assert_eq!(stack.pop(), Some("second"));
assert_eq!(stack.pop(), Some("first"));
assert_eq!(stack.pop(), None);
push(value)addsvalueto the end of the vector.pop()removes and returns the final value asOption<T>.last()borrows the final value asOption<&T>without removing it.
For a queue, import and use VecDeque<T>:
use std::collections::VecDeque;
let mut queue: VecDeque<&str> = VecDeque::new();
queue.push_back("first");
queue.push_back("second");
assert_eq!(queue.front(), Some(&"first"));
assert_eq!(queue.back(), Some(&"second"));
assert_eq!(queue.pop_front(), Some("first"));
assert_eq!(queue.pop_front(), Some("second"));
assert_eq!(queue.pop_front(), None);
push_back(value)adds an item to the back of the queue.pop_front()removes and returns the item at the front.front()andback()borrow an item without removing it.
The Option return type handles an empty collection safely: None means there was no value to return.
Step by Step Execution
Consider this queue:
use std::collections::VecDeque;
let mut jobs = VecDeque::new();
jobs.push_back("email");
jobs.push_back("report");
let next = jobs.pop_front();
Execution trace:
VecDeque::new()creates an empty queue:[].push_back("email")adds a job at the back:["email"].push_back("report")adds another job after it:["email", "report"].pop_front()removes the front job, which is"email".nextbecomesSome("email"), and the queue now contains["report"].
Now compare a stack:
let mut undo_stack = Vec::new();
undo_stack.push("typed A");
undo_stack.push("typed B");
let action = undo_stack.pop();
After the two pushes, the vector is ["typed A", "typed B"]. pop() removes the final item, so action is Some("typed B"). This is LIFO behavior.
Real World Use Cases
Vec<T> as a stack is useful for:
- Undo history in an editor: undo the newest action first.
- Parsing nested expressions, parentheses, or markup tags.
- Depth-first search through a tree or graph.
- Tracking function-like work that must be completed in reverse order.
VecDeque<T> as a queue is useful for:
- Processing web jobs in arrival order.
- Breadth-first search through a tree or graph.
- Buffering messages, events, or log records.
- Scheduling tasks that should run in the order they were submitted.
- Maintaining a fixed-size recent-history buffer by adding at the back and removing from the front.
Real Codebase Usage
In real Rust projects, stacks and queues are often kept private behind domain-specific functions instead of exposing collection operations everywhere.
A queue-processing loop commonly handles empty queues through Option:
use std::collections::VecDeque;
fn process_all(mut tasks: VecDeque<String>) {
while let Some(task) = tasks.pop_front() {
println!("Processing {task}");
}
}
while let Some(...) stops naturally when the queue is empty. It avoids unsafe indexing and avoids manually checking the length.
For a bounded queue, remove old values after adding a new one:
use std::collections::VecDeque;
fn add_recent(items: &mut VecDeque<String>, item: String, limit: usize) {
items.push_back(item);
if items.len() > limit {
items.pop_front();
}
}
For stacks, developers often use a Vec with guard-style handling of an empty stack:
fn undo(history: &mut Vec<String>) -> Option<String> {
history.pop()
}
Returning Option<String> makes the possibility of "nothing to undo" explicit to callers.
Common Mistakes
Using Vec::remove(0) for a busy queue
This works functionally, but it is usually inefficient because every remaining element must shift left.
let mut queue = vec!["first", "second", "third"];
let first = queue.remove(0); // Avoid for a frequently used FIFO queue
Use VecDeque instead:
use std::collections::VecDeque;
let mut queue = VecDeque::from(["first", "second", "third"]);
let first = queue.pop_front();
Reversing the queue ends
This code behaves like a stack, not a FIFO queue:
use std::collections::VecDeque;
let mut queue = VecDeque::new();
queue.push_back(1);
queue.push_back(2);
let value = queue.pop_back(); // Removes 2 first: LIFO
For FIFO behavior, pair push_back with pop_front.
Treating pop() as if it always returns a value
pop() and pop_front() return Option<T> because a collection may be empty.
let mut stack: Vec<i32> = Vec::new();
let value = stack.pop(); // None
Handle None with match, if let, while let, or an appropriate fallback such as unwrap_or.
Using indexing to inspect an empty collection
let stack: Vec<i32> = Vec::new();
let top = stack[stack.len() - 1]; // Panics due to underflow/out-of-bounds access
Use last() for stacks or front() and back() for queues. These return Option safely.
Comparisons
| Need | Recommended collection | Add operation | Remove operation | Why |
|---|---|---|---|---|
| LIFO stack | Vec<T> | push(value) | pop() | Efficient at the end of the vector |
| FIFO queue | VecDeque<T> | push_back(value) | pop_front() | Efficient at both ends |
| Double-ended work list | VecDeque<T> | push_front or push_back | pop_front or pop_back | Can add and remove at either end |
| Fixed-key lookup | HashMap<K, V> | insert by key | remove by key | Not ordered like a stack or queue |
Vec versus VecDeque:
Vecstores values contiguously and is often the default growable sequence.VecDequeis a double-ended queue. Its internal layout allows efficient front and back operations.- Choose
VecDequewhen your design regularly removes from the front.
A VecDeque can technically act as a stack too, but Vec is simpler and more conventional when only end-based stack operations are needed.
Cheat Sheet
// Stack: LIFO
let mut stack: Vec<i32> = Vec::new();
stack.push(10); // add to top
let top = stack.last(); // Option<&i32>, inspect top
let item = stack.pop(); // Option<i32>, remove top
// Queue: FIFO
use std::collections::VecDeque;
let mut queue: VecDeque<i32> = VecDeque::new();
queue.push_back(10); // add at back
let first = queue.front(); // Option<&i32>, inspect front
let last = queue.back(); // Option<&i32>, inspect back
let item = queue.pop_front(); // Option<i32>, remove front
- Stack rule: use
push+poponVec. - Queue rule: use
push_back+pop_frontonVecDeque. - All pop and peek methods return
Optionbecause the collection might be empty. - Avoid
Vec::remove(0)for a frequently used queue; it shifts remaining elements. VecDequealso offerspush_front()andpop_back()when both ends are needed.
FAQ
Does Rust have a built-in stack type?
Rust does not provide a separate standard-library Stack type. Use Vec<T> with push() and pop().
Does Rust have a built-in queue type?
Rust does not provide a C++-style std::queue adapter. Use std::collections::VecDeque<T> with push_back() and pop_front().
Why is Vec good for a stack?
Adding to and removing from the end of a Vec is efficient, which matches normal stack operations.
Why should I not use Vec as a FIFO queue?
Removing from index 0 requires shifting the remaining values. For repeated queue operations, use VecDeque.
What does pop_front() return when the queue is empty?
It returns None. When an item exists, it returns Some(item).
Can VecDeque be used as a stack?
Yes. You can use push_back() and pop_back(), but Vec is usually the clearer choice for a simple stack.
How do I look at the next queue item without removing it?
Call queue.front(). It returns Option<&T>.
How do I look at the top stack item without removing it?
Call stack.last(). It returns Option<&T>.
Mini Project
Description
Build a small support-ticket processor. Incoming tickets are processed in the order they arrive, while recently completed ticket IDs are stored in an undo-style stack. This demonstrates a FIFO VecDeque queue and a LIFO Vec stack in one practical program.
Goal
Process queued tickets in FIFO order and undo the most recently completed ticket.
Requirements
Create a VecDeque<String> containing at least three incoming tickets.
Process tickets by removing them from the front of the queue.
Store each completed ticket in a Vec<String> history stack.
Undo and print the most recently completed ticket.
Print a clear message if there is no completed ticket to undo.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.