static_cell — Crate 詳細
static_cell
静的に確保したメモリ領域を実行時に一度だけ初期化し、'static 参照として取得するための no_std / no_alloc 対応 crate。組み込み環境で &'static mut T が必要な初期化処理に適しています。
A no_std and no_alloc crate for reserving memory at compile time, initializing it at runtime, and obtaining a 'static reference to the initialized value.
概要
static_cell は、コンパイル時に静的なメモリ領域を予約し、実行時に一度だけ値を初期化して 'static 参照を得るための crate です。組み込み Rust では、executor、driver、peripheral state、DMA バッファ、通信スタックなどが &'static mut T を要求することがあります。しかし static mut を直接使うと unsafe が必要になり、Box::leak() は alloc が必要です。static_cell は no_std / no_alloc 環境でこの問題を扱うための実用的な選択肢です。
主な用途
代表的な用途は、起動時にしか初期化しないグローバルなリソース、ヒープを使わない固定バッファ、Embassy executor や driver state のような 'static ライフタイムを要求するオブジェクトの保持です。値は静的メモリ上に置かれ、初期化後はプログラムの残りの期間にわたって有効な参照として扱えます。
主要 API
StaticCell<T> は空の静的セルを定義し、init()、init_with()、try_init() などで実行時に初期化します。init() は値を渡して初期化し、init_with() は closure で値を生成します。大きな配列や構造体では init_with() を使うことで、初期化時のスタック使用量を抑えやすくなります。try_init() は二重初期化時に panic せず None を返すため、ライブラリコードや初期化順序が複雑なアプリケーションで便利です。
ConstStaticCell<T> は、値を const context で作れる場合に使えます。コンパイル時に値を持ったセルを作り、実行時に take() で &'static mut T として取得できます。
no_std / no_alloc での意義
static_cell は no_std / no_alloc 対応です。そのため、Cortex-M、RISC-V、nRF52、STM32、RP2040 などのベアメタル環境や、ヒープを使わないファームウェア設計と相性がよい crate です。Box::leak() のような alloc 前提の方法が使えない環境でも、静的メモリを利用して 'static 参照を得られます。
critical-section との関係
この crate は portable-atomic を利用します。ネイティブ atomic がないターゲットでは、プロジェクト側で critical-section の実装 crate を導入する必要があります。たとえば Cortex-M 系では、使用している HAL、RTOS、Embassy 構成、またはターゲット crate がどの critical-section 実装を提供するかを確認する必要があります。Cargo workspace で複数の embedded crate を組み合わせる場合は、critical-section 実装 feature の重複や未設定に注意してください。
代替手段との比較
alloc が使える場合は Box::leak() が選択肢になります。unsafe を許容する場合は static mut と MaybeUninit<T> を直接使うこともできます。ただし、これらは no_alloc 環境で使えなかったり、unsafe の扱いが難しくなったりします。&'static T だけでよい場合は OnceCell や OnceLock も候補になりますが、OnceLock は std 前提であり、&'static mut T が必要なケースでは static_cell の方が目的に合います。
組み込み Rust での設計上の注意
StaticCell は一度初期化すると、基本的に解放や再初期化をしない設計です。そのため、起動時に一度だけ生成し、システム終了まで保持するリソースに向いています。一方で、テストごとに状態をリセットしたいケースや、動的に生成・破棄したいリソースには向きません。グローバルな静的状態を増やしすぎると依存関係が見えにくくなるため、ボード初期化層や runtime 初期化層に責務を集約すると保守しやすくなります。
Embassy との相性
Embassy 系のコードでは executor、task、driver、peripheral state などで 'static ライフタイムが必要になる場面があります。static_cell はそのようなオブジェクトを起動時に初期化し、安全に長寿命参照として渡す用途に適しています。特に micro:bit v2、nRF52、STM32 などの no_std async firmware では、初期化時の所有権とライフタイムの整理に役立ちます。
コード例
StaticCell::new() で静的領域を確保し、init() で実行時に一度だけ初期化します。返り値は &'static mut T です。
use static_cell::StaticCell;
static SOME_INT: StaticCell<u32> = StaticCell::new();
fn main() { let x: &'static mut u32 = SOME_INT.init(42);
assert_eq!(*x, 42);
*x += 1; assert_eq!(*x, 43);}大きな型では init() より init_with() が適しています。closure の戻り値で初期化できるため、値を StaticCell 内で構築しやすく、スタック使用量を抑えやすくなります。
use static_cell::StaticCell;
static BUFFER: StaticCell<[u8; 4096]> = StaticCell::new();
fn main() { let buffer: &'static mut [u8; 4096] = BUFFER.init_with(|| [0u8; 4096]);
buffer[0] = 0x12; buffer[1] = 0x34;
assert_eq!(buffer[0], 0x12); assert_eq!(buffer[1], 0x34);}try_init() は、すでに初期化済みの場合に panic せず None を返します。初期化順序が複雑なファームウェアやライブラリコードで扱いやすい API です。
use static_cell::StaticCell;
static VALUE: StaticCell<u32> = StaticCell::new();
fn main() { let first = VALUE.try_init(10); assert!(first.is_some()); assert_eq!(*first.unwrap(), 10);
let second = VALUE.try_init(20); assert!(second.is_none());}値を const context で構築できる場合は ConstStaticCell を使えます。実行時に take() で &'static mut T として取得します。
use static_cell::ConstStaticCell;
static VALUE: ConstStaticCell<u32> = ConstStaticCell::new(123);
fn main() { let value: &'static mut u32 = VALUE.take();
assert_eq!(*value, 123);
*value = 456; assert_eq!(*value, 456);}