Vec<T> in Depth

push() and pop()

The .push() and .pop() methods append an element to the back of a Vec<T> and remove the last element from the back, allowing Vec<T> to act as an efficient LIFO (Last-In, First-Out) stack.

Signatures and Complexity

impl<T> Vec<T> {
    pub fn push(&mut self, value: T);
    pub fn pop(&mut self) -> Option<T>;
    pub fn pop_if(&mut self, predicate: impl FnOnce(&mut T) -> bool) -> Option<T>;
}
Before: [ 10, 20, 30 | -- ]  (len = 3, cap = 4)
                       ▲
v.push(40): writes 40 at [3], increments len to 4:
        [ 10, 20, 30, 40 ]   (len = 4, cap = 4)
                       │
v.pop(): decrements len to 3 and moves 40 out as Some(40):
        [ 10, 20, 30 | -- ]  (len = 3, cap = 4)
Method Time Complexity Effect on len and capacity
v.push(val) Amortized $O(1)$ ($O(n)$ only when len == capacity) Increments len by 1; grows capacity if full
v.pop() Guaranteed $O(1)$ Decrements len by 1 if non-empty; never shrinks capacity
v.pop_if(pred) Guaranteed $O(1)$ Pops and returns the last element only if pred(&mut last) returns true

Using Vec<T> as a LIFO Stack

fn is_balanced_brackets(expr: &str) -> bool {
    let mut stack = Vec::new();

    for ch in expr.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 main() {
    for sample in ["{[()]}", "{[(])}", "(()"] {
        println!("{sample:<8} -> balanced: {}", is_balanced_brackets(sample));
    }
}
{[()]}   -> balanced: true
{[(])}   -> balanced: false
(()      -> balanced: false

Conditional Popping with pop_if() (Rust 1.86+)

Instead of checking v.last() and then calling v.pop().unwrap(), v.pop_if(|last| ...) inspects (and optionally mutates) the final element and pops it only when the predicate returns true:

fn main() {
    let mut tasks = vec![10, 25, 99];

    let urgent = tasks.pop_if(|&mut prio| prio >= 50);
    let next_urgent = tasks.pop_if(|&mut prio| prio >= 50);

    println!("First pop_if:  {urgent:?}");
    println!("Second pop_if: {next_urgent:?}");
    println!("Remaining:     {tasks:?}");
}
First pop_if:  Some(99)
Second pop_if: None
Remaining:     [10, 25]