Collections and Iterators in Rust: Processing Production Line Data Efficiently
Vec: The Dynamic Array
A Vec<T> is the most common collection in Rust. It stores elements of the same type in a growable, contiguous block of memory. In industrial systems, vectors are ideal for accumulating sensor readings over time.
fn main() {
// Create a vector to store temperature readings (Celsius)
let mut readings: Vec<f64> = Vec::new();
// Simulate receiving readings from a furnace sensor
readings.push(872.5);
readings.push(875.0);
readings.push(870.3);
readings.push(880.1);
// Access by index (panics if out of bounds)
println!("First reading: {} C", readings[0]);
// Safe access with .get() returns Option<&T>
if let Some(val) = readings.get(10) {
println!("Reading: {}", val);
} else {
println!("No reading at index 10");
}
// Remove and return the last element
let last = readings.pop(); // Some(880.1)
println!("Removed last: {:?}, length now: {}", last, readings.len());
}
You can also initialize a vector with the vec! macro: let alarms = vec![101, 204, 307];
HashMap<K, V>: Key-Value Storage
A HashMap lets you associate keys with values. In a factory, you might map each sensor ID to its most recent reading.
use std::collections::HashMap;
fn main() {
let mut last_reading: HashMap<String, f64> = HashMap::new();
// Insert readings keyed by sensor ID
last_reading.insert("TEMP-01".to_string(), 74.5);
last_reading.insert("PRESS-03".to_string(), 2.1);
last_reading.insert("VIBR-07".to_string(), 0.45);
// Look up a sensor reading
match last_reading.get("PRESS-03") {
Some(val) => println!("Pressure sensor: {} bar", val),
None => println!("Sensor not found"),
}
// Update only if key is missing using entry API
last_reading.entry("TEMP-01".to_string()).or_insert(0.0);
// TEMP-01 already exists, so the value stays 74.5
// Iterate over all entries
for (sensor_id, value) in &last_reading {
println!("{}: {}", sensor_id, value);
}
}
Other Collections: VecDeque, BTreeMap, and HashSet
Rust offers several specialized collections in std::collections:
- VecDeque -- A double-ended queue, efficient for push/pop at both ends. Use it for a sliding window of recent readings.
- BTreeMap<K, V> -- Like HashMap but keeps keys sorted. Useful when you need readings ordered by timestamp.
- HashSet -- A set of unique values. Great for tracking which machines have reported an alarm.
use std::collections::{VecDeque, BTreeMap, HashSet};
fn main() {
// Sliding window of last 5 pressure readings
let mut window: VecDeque<f64> = VecDeque::with_capacity(5);
window.push_back(2.1);
window.push_back(2.3);
// When full, remove oldest before adding new
if window.len() >= 5 { window.pop_front(); }
// Sorted map of timestamp -> reading
let mut log: BTreeMap<u64, f64> = BTreeMap::new();
log.insert(1700000000, 72.0);
log.insert(1700000060, 73.5);
// Iterating gives entries in ascending key order
// Track which machine IDs triggered alarms today
let mut alarmed: HashSet<u32> = HashSet::new();
alarmed.insert(101);
alarmed.insert(204);
alarmed.insert(101); // duplicate ignored
println!("Distinct machines with alarms: {}", alarmed.len()); // 2
}
Iterators: The Lazy Processing Pipeline
Iterators in Rust are lazy -- they do no work until you consume them. Every collection can produce an iterator with .iter(). You then chain adapter methods to transform the data.
fn main() {
let readings = vec![72.0, 85.3, 91.7, 68.4, 77.2];
// .iter() borrows each element
// .map() transforms each value
// .collect() consumes the iterator into a new collection
let fahrenheit: Vec<f64> = readings.iter()
.map(|c| c * 9.0 / 5.0 + 32.0)
.collect();
println!("Fahrenheit: {:?}", fahrenheit);
}
Key iterator methods: .map() transforms, .filter() selects, .enumerate() adds indices, .sum() totals, .count() counts elements, and .collect() builds a new collection.
Chaining Iterator Adapters
The real power of iterators emerges when you chain multiple adapters together. This creates a processing pipeline that reads naturally and runs efficiently.
fn main() {
let readings = vec![72.0, 85.3, 110.7, 68.4, 97.2, 105.0, 71.8];
let threshold = 100.0;
// Find readings above threshold and convert to alert strings
let alerts: Vec<String> = readings.iter()
.enumerate()
.filter(|(_, &val)| val > threshold)
.map(|(i, val)| format!("ALERT: reading[{}] = {:.1} C exceeds limit", i, val))
.collect();
for alert in &alerts {
println!("{}", alert);
}
// ALERT: reading[2] = 110.7 C exceeds limit
// ALERT: reading[5] = 105.0 C exceeds limit
}
Because iterators are lazy, Rust processes each element through the entire chain before moving to the next. No intermediate vectors are created.
Practical Example: Analyzing a Batch of Production Readings
Combining collections and iterators to analyze a production batch:
use std::collections::HashMap;
fn main() {
// Simulated readings: (machine_id, temperature)
let batch: Vec<(&str, f64)> = vec![
("CNC-01", 68.0), ("CNC-01", 72.5), ("CNC-01", 110.0),
("LATHE-02", 55.0), ("LATHE-02", 58.3),
("CNC-01", 69.0), ("LATHE-02", 57.1),
];
// Group readings by machine
let mut grouped: HashMap<&str, Vec<f64>> = HashMap::new();
for (id, temp) in &batch {
grouped.entry(id).or_insert_with(Vec::new).push(*temp);
}
// Compute average and detect outliers per machine
let limit = 100.0;
for (machine, temps) in &grouped {
let count = temps.len() as f64;
let avg = temps.iter().sum::<f64>() / count;
let outliers: Vec<&f64> = temps.iter().filter(|&&t| t > limit).collect();
println!("{}: avg = {:.1} C, readings = {}", machine, avg, temps.len());
if !outliers.is_empty() {
println!(" WARNING: {} outlier(s) above {} C", outliers.len(), limit);
}
}
}
Summary
- Vec is the go-to collection for ordered, growable lists of sensor data.
- HashMap<K, V> maps keys (sensor IDs) to values (readings) for fast lookup.
- VecDeque, BTreeMap, and HashSet serve specialized needs like sliding windows, sorted logs, and unique alarm tracking.
- Iterators are lazy pipelines built with
.iter(),.map(),.filter(), and.collect(). - Chaining adapters creates efficient, readable data processing without intermediate allocations.
- Combining collections with iterators lets you group, aggregate, and analyze production data cleanly.