Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

決定論的メモリ

Heapless コレクションによる決定論的メモリ

heapless-badge cat-no-std-badge cat-data-structures-badge

安全性が重要なシステムやリアルタイムシステムでは、ヒープの使用を完全に禁止していることが少なくありません。グローバルアロケータは非決定的であり、割り当て時間は一定ではなく、長時間稼働するファームウェアでは断片化や、気づかれない OOM 障害のリスクがあります。

heapless は、データをコンパイル時に容量が固定された スタック(または static)上に保持する VecStringDeque、およびマップ型を提供します。 容量を超えて追加しようとすると、パニックや割り当てを行う代わりに Err が返されるため、呼び出し側がそれをどう処理するかを決定できます。

std の型heapless の対応型保証
Vec<T>heapless::Vec<T, N>要素数は最大 N、アロケータ不要
Stringheapless::String<N>最大 N バイト、アロケータ不要
VecDeque<T>heapless::Deque<T, N>固定容量のリングバッファ
HashMap<K,V>heapless::IndexMap<K,V,S,N>固定容量のハッシュマップ

以下の例では、スタックに割り当てられたイベントログを構築し、 容量制限の適用を示しています。これは、組み込みシステムやリアルタイムシステムで 求められる、予測可能で一定メモリの動作の一例です。

use heapless::{String, Vec};
use thiserror::Error;

#[derive(Debug, Error)]
enum LogError {
    #[error("log full (capacity exceeded)")]
    Full,
    #[error("log is empty")]
    Empty,
    #[error(transparent)]
    Fmt(#[from] core::fmt::Error),
}

/// A fixed-capacity event log that never touches the heap.
///
/// In real-time and safety-critical systems the global allocator is
/// forbidden because allocation time is unbounded and fragmentation
/// can cause silent OOM failures in long-running firmware.
///
/// [`heapless::Vec`] stores up to `N` elements on the stack (or in a
/// `static`).  The capacity is fixed at compile time, so the memory
/// footprint is constant and predictable.
struct EventLog<const N: usize> {
    entries: Vec<Event, N>,
}

#[derive(Debug, Clone)]
struct Event {
    timestamp_ms: u32,
    code: u16,
}

impl<const N: usize> EventLog<N> {
    /// Creates an empty log.
    fn new() -> Self {
        Self {
            entries: Vec::new(),
        }
    }

    /// Records an event.  Returns `Err` if the log is full instead
    /// of panicking or allocating—the caller decides what to do.
    fn record(&mut self, timestamp_ms: u32, code: u16) -> Result<(), LogError> {
        let event = Event { timestamp_ms, code };
        self.entries.push(event).map_err(|_| LogError::Full)
    }

    /// Returns how many events have been recorded.
    fn len(&self) -> usize {
        self.entries.len()
    }

    /// Returns the most recent event, if any.
    fn latest(&self) -> Result<&Event, LogError> {
        self.entries.last().ok_or(LogError::Empty)
    }
}

/// Formats a sensor label without heap allocation.
///
/// [`heapless::String<N>`] works like `std::string::String` but
/// stores up to `N` bytes on the stack.  `write!` returns `Err` if
/// the formatted text would exceed capacity.
fn format_label(sensor_id: u16, value: f32) -> Result<String<32>, LogError> {
    use core::fmt::Write;
    let mut buf: String<32> = String::new();
    write!(buf, "S{sensor_id}={value:.1}")?;
    Ok(buf)
}

fn main() -> Result<(), LogError> {
    // A log that holds at most 8 events — zero heap allocation.
    let mut log: EventLog<8> = EventLog::new();

    log.record(100, 0x01)?;
    log.record(200, 0x02)?;
    log.record(300, 0xFF)?;

    println!("logged {} events", log.len());
    let latest = log.latest()?;
    println!(
        "latest: timestamp={}ms code=0x{:02X}",
        latest.timestamp_ms, latest.code
    );

    // Stack-allocated string formatting.
    let label = format_label(42, 3.14)?;
    println!("label: {label}");

    // Demonstrate capacity enforcement — the 9th push returns Err.
    let mut full_log: EventLog<2> = EventLog::new();
    full_log.record(0, 1)?;
    full_log.record(1, 2)?;
    assert!(full_log.record(2, 3).is_err());
    println!("overflow correctly rejected");

    Ok(())
}