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

パニックしないことの保証

コンパイル時の No-Panic 保証

no-panic-badge cat-no-std-badge cat-rust-patterns-badge

安全性が重要なシステム—自動車のブレーキ、医療機器、航空宇宙—では、 panic! は単なるクラッシュではなく、致命的な障害です。array[i] のような 標準的な操作では、境界外アクセス時にパニックする隠れた境界チェックが挿入されます。

#[no_panic] 属性マクロは、関数がパニックするコードパスに決して到達しないことを リンク時にコンパイラに証明させます。証明できなければ、ビルドは失敗します。

パニックするパターン安全な代替手段
slice[i]slice.get(i) を網羅的な match とともに使用
for i in 0..len { slice[i] }for &v in slice(イテレータ)
value.unwrap()match / if let / ?
a / b(整数、b が 0 の可能性がある)除算前に b をチェックする

以下の例は、3 つのパニックしない関数—集計、検索、センサーの正規化—を示しており、 それぞれが #[no_panic] によってコンパイル時に証明されます。

use no_panic::no_panic;

/// Sums a slice without any operation that could panic.
///
/// In safety-critical code, a `panic!` is a catastrophic failure.
/// Standard indexing like `slice[i]` inserts a bounds check that
/// calls `panic!` on out-of-bounds access.  Iterators avoid this
/// entirely—the compiler can prove no panic path exists.
#[no_panic]
fn safe_sum(values: &[i32]) -> i64 {
    let mut total: i64 = 0;
    for &v in values {
        total += v as i64;
    }
    total
}

/// Looks up a value by index, returning `None` instead of panicking.
///
/// Using `get()` + exhaustive pattern matching guarantees every code
/// path is handled.  The `#[no_panic]` attribute makes the compiler
/// **prove** it at link time—if any hidden panic path remains, the
/// build fails.
#[no_panic]
fn safe_lookup(data: &[u8], index: usize) -> Option<u8> {
    match data.get(index) {
        Some(&val) => Some(val),
        None => None,
    }
}

/// Clamps a sensor reading into a valid range without panicking.
///
/// Real-world example: an ADC returns a raw `u16` that must be
/// mapped to 0–100 %.  Using `clamp` and simple arithmetic keeps
/// the function panic-free.
#[no_panic]
fn normalize_sensor(raw: u16, min: u16, max: u16) -> f32 {
    if max == min {
        return 0.0;
    }
    let clamped = raw.clamp(min, max);
    (clamped - min) as f32 / (max - min) as f32
}

fn main() {
    let readings = [10, 20, 30, 40, 50];

    let total = safe_sum(&readings);
    println!("sum = {total}");
    assert_eq!(total, 150);

    let val = safe_lookup(&[0xAA, 0xBB, 0xCC], 1);
    println!("lookup index 1 = {val:?}");
    assert_eq!(val, Some(0xBB));

    let miss = safe_lookup(&[0xAA, 0xBB, 0xCC], 99);
    println!("lookup index 99 = {miss:?}");
    assert_eq!(miss, None);

    let pct = normalize_sensor(2048, 0, 4095);
    println!("sensor = {pct:.2}%");
    assert!((pct - 0.5).abs() < 0.01);
}