Home Wiki Programming & Logic Collections and Iterators in Rust: Processing Production Line Data Efficiently
Programming & Logic

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.
Vec HashMap iterators map filter collect المتجهات خريطة التجزئة المكررات التصفية التحويل معالجة البيانات