There are several ways to implement or use a stack in Rust. A stack is a data structure that allows us to store data in LIFO (last-in-first-out). Rust has no stack data structure, but it offers data structures with similar behavior – Vec, LinkedList, and VecQueue. The typical operations for stacks are push, peek, and pop. The push operation allows us to add new items to the stack; the peek operation lets us examine the object on top of the stack. Finally, the pop operation enables us to remove an item from the top of the stack.
Our Use-case to Use Stack in Rust
For this post, our use-case is simple – add or remove plates from a stack of plates in Rust. When we add or remove a plate from a pile, we do it on the top, which is evident, especially when there is already at least one plate.
Rust Vec Example
The Rust Vec is a growable array type. We can use it either as a list or a stack in Rust. As a list, we add or remove items randomly by specifying indexes. Consider the following codes.
1 2 3 4 5 6 7 8 9 10 11 | fn main() { let mut list = Vec::new(); list.insert(0, 1); list.insert(0, 2); list.insert(1, 3); println!("{:?}", list); list.remove(2 as usize); println!("{:?}", list); } |
The codes create an empty using Vec and insert the values 1,2 and 3 in various locations in the list. When we remove an item, we specify the index of the item. The codes generate the following output.
1 2 | [2, 3, 1] [2, 3] |
On the other hand, we can use it as a stack to add or remove items from one end of the list, which we usually refer to as the top. Consider the following codes.
1 2 3 4 5 6 7 8 9 10 11 | fn main() { let mut list = Vec::new(); list.push( 1); list.push( 2); list.push( 3); println!("Before peek: {:?}", list); println!("peeked {:?}", list.last()); println!("After peek but before pop: {:?}", list); list.pop(); println!("After pop: {:?}", list); } |
These codes output the following.
1 2 3 4 | Before peek: [1, 2, 3] peeked Some(3) After peek but before pop: [1, 2, 3] After pop: [1, 2] |
Rust LinkedList Example
Alternatively, we can use LinkedList in Rust to simulate a stack. If we change the codes from the earlier section, we will get the following codes.
1 2 3 4 5 6 7 8 9 10 11 12 13 | use std::collections::LinkedList; fn main() { let mut list = LinkedList::new(); list.push_back( 1); // or push_front list.push_back( 2); list.push_back( 3); println!("Before peek: {:?}", list); println!("peeked {:?}", list.back()); // or front println!("After peek but before pop: {:?}", list); list.pop_back(); // or push_front println!("After pop: {:?}", list); } |
With Rust LinkedList, we can choose which location to process the items – front or back. To use it as a stack, we choose either. But when we use it as a queue (for another post), we use both.
The codes generate the following result.
1 2 3 4 | Before peek: [1, 2, 3] peeked Some(3) After peek but before pop: [1, 2, 3] After pop: [1, 2] |
Use VecQueue As Stack In Rust
Lastly, we have the VecQueue, and we can use it as a stack in Rust with virtually no changes if we are coming from using LinkedList. Take a look at the codes for VecQueue and LinkedList.
1 2 3 4 5 6 7 8 9 10 11 12 13 | use std::collections::{VecDeque}; fn main() { let mut list = VecDeque::new(); list.push_back( 1); // or push_front list.push_back( 2); list.push_back( 3); println!("Before peek: {:?}", list); println!("peeked {:?}", list.back()); // or front println!("After peek but before pop: {:?}", list); list.pop_back(); // or push_front println!("After pop: {:?}", list); } |
Can you spot the difference?