1039-stack-via-vec — Stack Using Vec
Tutorial
The Problem
A stack (LIFO — last in, first out) is one of the most fundamental data structures: it underlies function call frames, expression evaluation, undo history, and DFS traversal. Implementing a stack efficiently requires O(1) push and pop at one end.
Rust's Vec<T> provides exactly this: push appends to the end (O(1) amortized) and pop removes from the end (O(1)). The back of a Vec is the top of the stack. No additional data structure is needed — Vec IS a stack.
🎯 Learning Outcomes
Vec::push and Vec::pop implement a LIFO stackVec in a newtype to provide a cleaner stack APIVec-based stacks to linked-list stacksCode Example
#![allow(clippy::all)]
// 1039: Stack Using Vec
// Vec's push/pop operate at the end — perfect LIFO stack
/// A simple stack wrapper around Vec
struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack { items: Vec::new() }
}
fn push(&mut self, value: T) {
self.items.push(value);
}
fn pop(&mut self) -> Option<T> {
self.items.pop()
}
fn peek(&self) -> Option<&T> {
self.items.last()
}
fn is_empty(&self) -> bool {
self.items.is_empty()
}
fn size(&self) -> usize {
self.items.len()
}
}
fn basic_stack() {
let mut s = Stack::new();
assert!(s.is_empty());
s.push(10);
s.push(20);
s.push(30);
assert_eq!(s.size(), 3);
assert_eq!(s.peek(), Some(&30));
assert_eq!(s.pop(), Some(30));
assert_eq!(s.pop(), Some(20));
assert_eq!(s.pop(), Some(10));
assert_eq!(s.pop(), None);
}
/// Vec directly as a stack (no wrapper needed)
fn vec_as_stack() {
let mut stack: Vec<i32> = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
assert_eq!(stack.last(), Some(&3));
assert_eq!(stack.pop(), Some(3));
assert_eq!(stack.len(), 2);
}
/// RPN (Reverse Polish Notation) calculator using a stack
fn eval_rpn(tokens: &[&str]) -> i32 {
let mut stack: Vec<i32> = Vec::new();
for &token in tokens {
match token {
"+" | "-" | "*" => {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
let result = match token {
"+" => a + b,
"-" => a - b,
"*" => a * b,
_ => unreachable!(),
};
stack.push(result);
}
n => stack.push(n.parse().unwrap()),
}
}
stack.pop().unwrap()
}
fn eval_test() {
// 3 4 + 2 * = (3 + 4) * 2 = 14
assert_eq!(eval_rpn(&["3", "4", "+", "2", "*"]), 14);
// 5 1 2 + 4 * + 3 - = 5 + (1+2)*4 - 3 = 14
assert_eq!(eval_rpn(&["5", "1", "2", "+", "4", "*", "+", "3", "-"]), 14);
}
/// Balanced parentheses checker
fn is_balanced(s: &str) -> bool {
let mut stack = Vec::new();
for ch in s.chars() {
match ch {
'(' | '[' | '{' => stack.push(ch),
')' => {
if stack.pop() != Some('(') {
return false;
}
}
']' => {
if stack.pop() != Some('[') {
return false;
}
}
'}' => {
if stack.pop() != Some('{') {
return false;
}
}
_ => {}
}
}
stack.is_empty()
}
fn balance_test() {
assert!(is_balanced("({[]})"));
assert!(is_balanced(""));
assert!(!is_balanced("({[})"));
assert!(!is_balanced("(("));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
basic_stack();
}
#[test]
fn test_vec_stack() {
vec_as_stack();
}
#[test]
fn test_rpn() {
eval_test();
}
#[test]
fn test_balanced() {
balance_test();
}
}Key Differences
Vec (contiguous memory, O(1) amortized push); OCaml's Stack uses a linked list (O(1) push, but pointer-chasing).Vec-as-stack is mutable and imperative.Vec::last() peeks without popping; OCaml's list head is accessible without modification.Vec grows in chunks (amortized O(1)); OCaml's linked list allocates one cons cell per push.OCaml Approach
OCaml's Stack module provides a mutable stack backed by a linked list. The functional alternative is just a list:
(* Functional stack: list is a stack *)
let push value stack = value :: stack
let pop = function [] -> None | x :: rest -> Some (x, rest)
let peek = function [] -> None | x :: _ -> Some x
OCaml's built-in Stack module uses a linked list, so each push allocates a new cons cell. Rust's Vec uses amortized-O(1) heap reallocation, which is cache-friendlier.
Full Source
#![allow(clippy::all)]
// 1039: Stack Using Vec
// Vec's push/pop operate at the end — perfect LIFO stack
/// A simple stack wrapper around Vec
struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack { items: Vec::new() }
}
fn push(&mut self, value: T) {
self.items.push(value);
}
fn pop(&mut self) -> Option<T> {
self.items.pop()
}
fn peek(&self) -> Option<&T> {
self.items.last()
}
fn is_empty(&self) -> bool {
self.items.is_empty()
}
fn size(&self) -> usize {
self.items.len()
}
}
fn basic_stack() {
let mut s = Stack::new();
assert!(s.is_empty());
s.push(10);
s.push(20);
s.push(30);
assert_eq!(s.size(), 3);
assert_eq!(s.peek(), Some(&30));
assert_eq!(s.pop(), Some(30));
assert_eq!(s.pop(), Some(20));
assert_eq!(s.pop(), Some(10));
assert_eq!(s.pop(), None);
}
/// Vec directly as a stack (no wrapper needed)
fn vec_as_stack() {
let mut stack: Vec<i32> = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
assert_eq!(stack.last(), Some(&3));
assert_eq!(stack.pop(), Some(3));
assert_eq!(stack.len(), 2);
}
/// RPN (Reverse Polish Notation) calculator using a stack
fn eval_rpn(tokens: &[&str]) -> i32 {
let mut stack: Vec<i32> = Vec::new();
for &token in tokens {
match token {
"+" | "-" | "*" => {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
let result = match token {
"+" => a + b,
"-" => a - b,
"*" => a * b,
_ => unreachable!(),
};
stack.push(result);
}
n => stack.push(n.parse().unwrap()),
}
}
stack.pop().unwrap()
}
fn eval_test() {
// 3 4 + 2 * = (3 + 4) * 2 = 14
assert_eq!(eval_rpn(&["3", "4", "+", "2", "*"]), 14);
// 5 1 2 + 4 * + 3 - = 5 + (1+2)*4 - 3 = 14
assert_eq!(eval_rpn(&["5", "1", "2", "+", "4", "*", "+", "3", "-"]), 14);
}
/// Balanced parentheses checker
fn is_balanced(s: &str) -> bool {
let mut stack = Vec::new();
for ch in s.chars() {
match ch {
'(' | '[' | '{' => stack.push(ch),
')' => {
if stack.pop() != Some('(') {
return false;
}
}
']' => {
if stack.pop() != Some('[') {
return false;
}
}
'}' => {
if stack.pop() != Some('{') {
return false;
}
}
_ => {}
}
}
stack.is_empty()
}
fn balance_test() {
assert!(is_balanced("({[]})"));
assert!(is_balanced(""));
assert!(!is_balanced("({[})"));
assert!(!is_balanced("(("));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
basic_stack();
}
#[test]
fn test_vec_stack() {
vec_as_stack();
}
#[test]
fn test_rpn() {
eval_test();
}
#[test]
fn test_balanced() {
balance_test();
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
basic_stack();
}
#[test]
fn test_vec_stack() {
vec_as_stack();
}
#[test]
fn test_rpn() {
eval_test();
}
#[test]
fn test_balanced() {
balance_test();
}
}
Deep Comparison
Stack Using Vec — Comparison
Core Insight
A stack is the simplest data structure, and both languages have a natural fit. OCaml's list is literally a stack (cons cells). Rust's Vec has push/pop at the end, which is amortized O(1) and contiguous in memory.
OCaml Approach
x :: xs = push, List.hd = peek, List.tl = popRust Approach
Vec<T> with push/pop/last — no wrapper neededpop() returns Option<T> for safe empty handlingComparison Table
| Feature | OCaml (list) | Rust (Vec) |
|---|---|---|
| Push | x :: xs O(1) | push(x) amortized O(1) |
| Pop | Pattern match O(1) | pop() → Option<T> O(1) |
| Peek | List.hd / pattern match | last() → Option<&T> |
| Memory | Linked cons cells | Contiguous array |
| Mutability | Immutable | Mutable |
| Cache friendly | No | Yes |
| Wrapper needed | Optional | Optional |
Exercises
min_stack that tracks the current minimum in O(1) by maintaining a parallel stack of minimums.evaluate_rpn(tokens: &[&str]) -> Result<i64, String> function that evaluates a Reverse Polish Notation expression using a Stack<i64>.is_balanced_parens(s: &str) -> bool that handles (), [], and {} using the stack.