決定論的メモリ
Heapless コレクションによる決定論的メモリ
安全性が重要なシステムやリアルタイムシステムでは、ヒープの使用を完全に禁止していることが少なくありません。グローバルアロケータは非決定的であり、割り当て時間は一定ではなく、長時間稼働するファームウェアでは断片化や、気づかれない OOM 障害のリスクがあります。
heapless は、データをコンパイル時に容量が固定された スタック(または static)上に保持する Vec、String、Deque、およびマップ型を提供します。
容量を超えて追加しようとすると、パニックや割り当てを行う代わりに Err が返されるため、呼び出し側がそれをどう処理するかを決定できます。
std の型 | heapless の対応型 | 保証 |
|---|---|---|
Vec<T> | heapless::Vec<T, N> | 要素数は最大 N、アロケータ不要 |
String | heapless::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(())
}