rust-no-std について
`#![no_std]` のライブラリやベアメタルファームウェアを開発・デバッグする Rust 開発者向けのスキルです。`core` と `alloc` の違い、アロケーターやパニックハンドラーの設定、ホストでのテストを解説します。
主な機能
- 01core、alloc、std の比較
- 02グローバルアロケーターの設定
- 03no_std のパニックハンドラー選択
- 04alloc を任意で使うライブラリ設計
- 05ホストでの no_std クレートのテスト
活用シーン
- 01std を使わないパーサーの構築
- 02no_std 環境での Vec の利用
- 03ノート PC での no_std ライブラリのテスト
- 04組み込みターゲット向けコードの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill rust-no-stdインストール後、Claude Code で/rust-no-stdを入力して読み込まれたか確認します。
詳細
Rust no_std
目的
エージェント向けに、#![no_std] の Rust 開発を説明します。core と alloc が std と比べて提供する機能、カスタムグローバルアロケーターの実装方法、組み込みターゲットでのパニックハンドラーの選択、ホストマシンで no_std クレートをテストする方法を解説します。
使用する場面
#![no_std] の Rust コードを記述またはデバッグするときに使用してください。対象は、組み込みターゲット向けのライブラリークレート、または std とリンクできないベアメタルファームウェアです。組み込み開発のワークフロー全体(probe-rs による書き込み、defmt ロギング、RTIC)については、skills/embedded/embedded-rust を使用してください。クロスコンパイルのターゲット設定については、skills/rust/rust-cross を使用してください。このスキルでは、特に no_std/core/alloc の境界とパニックハンドラーの選択に焦点を当てます。
例
- 「std なしで動作するパーサークレートが必要です」→
#![no_std]を使った構成にし、alloc API をフィーチャーゲートし、コア API では借用スライスを使用します - 「no_std 環境で Vec を使うにはどうすればよいですか?」→
allocフィーチャーを追加し、グローバルアロケーター(例:linked-list-allocator)を用意して、alloc::vec::Vecを使用します - 「ノート PC で no_std クレートをテストするにはどうすればよいですか?」→
#![cfg_attr(not(test), no_std)]を使ってテストモードでは std を使えるようにするか、cargo test --target x86_64-unknown-linux-gnuを実行します
ワークフロー
1. no_std クレートの構成
// src/lib.rs
#![no_std]
// core is always available (no OS needed)
use core::fmt;
use core::mem;
use core::slice;
// alloc: heap collections — requires a global allocator
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{vec::Vec, string::String, boxed::Box, format};
pub fn add(a: u32, b: u32) -> u32 {
a + b
}
# Cargo.toml
[features]
default = []
alloc = [] # opt-in to heap allocation
[dependencies]
# no_std-compatible dependencies only
2. core、alloc、std の比較
| クレート | OS が必要 | ヒープが必要 | 提供する機能 |
|---|---|---|---|
core | いいえ | いいえ | プリミティブ型、トレイト、イテレーター、fmt、mem、ptr、slice、option、result |
alloc | いいえ | はい(アロケーター) | Vec、String、Box、Arc、Rc、HashMap(グローバルアロケーターが必要) |
std | はい | はい | core + alloc のすべて、および OS API(スレッド、ファイル、ソケット、環境変数) |
std は core と alloc のすべてを再エクスポートするため、std が利用できる場合は use std::fmt と use core::fmt は同等です。
core だけで利用できる機能(ヒープなし、OS なし):
// These work in no_std:
core::fmt::Write // trait for write! macro
core::iter // iterators
core::ops // operators (+, -, *, Deref, etc.)
core::option::Option
core::result::Result
core::mem::{size_of, align_of, swap, replace}
core::ptr::{read, write, null, NonNull}
core::slice, core::str
core::sync::atomic // atomic types
core::cell::{Cell, UnsafeCell, RefCell}
core::cmp, core::convert, core::clone, core::default
core::num // numeric conversions
core::panic::PanicInfo // for panic handler
3. カスタムグローバルアロケーター
no_std で alloc クレートを使用するには、グローバルアロケーターを用意します。
// src/allocator.rs — embedded allocator using linked_list_allocator
use linked_list_allocator::LockedHeap;
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();
pub fn init_heap(heap_start: usize, heap_size: usize) {
unsafe {
ALLOCATOR.lock().init(heap_start as *mut u8, heap_size);
}
}
[dependencies]
linked-list-allocator = { version = "0.10", default-features = false }
// src/main.rs (bare-metal)
#![no_std]
#![no_main]
extern crate alloc;
use alloc::vec::Vec;
mod allocator;
// In init code (after BSS/data init):
allocator::init_heap(0x20010000, 0x10000); // 64KB heap at RAM+64KB
// Now alloc types work:
let mut v: Vec<u32> = Vec::new();
v.push(42);
一般的な組み込み向けアロケータークレート:
linked-list-allocator:汎用、no_stdbuddy-alloc:2 のべき乗を利用したバディシステムdlmalloc:Doug Lea の malloc の移植版talc:高速で、組み込み用途に適しています
4. パニックハンドラー
no_std では、Rust が必須としているパニックハンドラーを用意する必要があります。
// Option 1: halt on panic (simplest, production)
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {} // spin forever
}
// Option 2: print panic info via defmt (embedded with debug probe)
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
defmt::error!("{}", defmt::Display2Format(info));
cortex_m::asm::udf(); // undefined instruction → hard fault
}
// Option 3: use a panic crate (in Cargo.toml)
// panic-halt = "0.2" — spin loop
// panic-reset = "0.1.1" — reset MCU
// panic-probe = "0.3" — defmt + probe-rs
5. 移植性のある no_std ライブラリーの作成
alloc の有無にかかわらず動作するように、ライブラリーを設計します。
#![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
pub struct Parser<'a> {
data: &'a [u8], // borrowed slice: no allocation needed
pos: usize,
}
impl<'a> Parser<'a> {
pub fn new(data: &'a [u8]) -> Self {
Parser { data, pos: 0 }
}
// Core API: return borrowed data, no allocation
pub fn next_token(&mut self) -> Option<&'a [u8]> { /* ... */ None }
// Alloc API: only when alloc feature is enabled
#[cfg(feature = "alloc")]
pub fn collect_all(&mut self) -> alloc::vec::Vec<&'a [u8]> {
let mut tokens = alloc::vec::Vec::new();
while let Some(tok) = self.next_token() {
tokens.push(tok);
}
tokens
}
}
6. ホストでの no_std のテスト
# Cargo.toml
[dev-dependencies]
std = [] # allow std in tests only (via cfg)
[features]
std = []
// lib.rs
#![cfg_attr(not(test), no_std)] // no_std except during tests
// Tests compile normally with std — only library code is no_std
または、別のテストハーネスを使用します。
# Run tests targeting the host (std available for test framework)
cargo test --target x86_64-unknown-linux-gnu
# Test with the actual embedded target using QEMU
cargo test --target thumbv7em-none-eabihf # fails: no test runner on bare metal
# Solution: use defmt-test or probe-run for on-target testing
# Or: architecture-neutral pure logic tests on host
# Check no_std compliance without hardware
cargo check --target thumbv7em-none-eabihf
cargo build --target thumbv7em-none-eabihf
関連スキル
- no_std で probe-rs、defmt、RTIC を使う場合は、
skills/embedded/embedded-rustを使用してください - クロスコンパイルのターゲット設定については、
skills/rust/rust-crossを使用してください - アロケーター実装で必要となる unsafe パターンについては、
skills/rust/rust-unsafeを使用してください - ベアメタルターゲットでのヒープ領域の配置については、
skills/embedded/linker-scriptsを使用してください