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

4.1 panic!

panic!이란?

panic!은 프로그램이 계속 실행될 수 없는 상황에서 즉시 종료하는 메커니즘입니다. 스택을 풀어내며(unwinding) 정리 코드를 실행하거나, 즉시 중단(abort)합니다.

fn main() {
    panic!("Something went terribly wrong!");
    // thread 'main' panicked at 'Something went terribly wrong!', src/main.rs:2:5
}

panic!이 발생하는 상황들

1. 명시적 panic! 호출

#![allow(unused)]
fn main() {
fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        panic!("Division by zero!");
    }
    a / b
}
}

2. 배열/Vec 범위 초과

fn main() {
    let v = vec![1, 2, 3];
    println!("{}", v[10]);
    // thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 10'
}

3. unwrap()이 None에 호출될 때

fn main() {
    let value: Option<i32> = None;
    let x = value.unwrap();
    // thread 'main' panicked at 'called `Option::unwrap()` on a `None` value'
}

4. expect()

fn main() {
    let value: Option<i32> = None;
    let x = value.expect("value must exist here");
    // thread 'main' panicked at 'value must exist here'
}

5. 정수 오버플로 (debug 모드)

fn main() {
    let x: u8 = 255;
    let y = x + 1;  // debug 모드에서 panic! (overflow)
                    // release 모드에서는 wrapping (0이 됨)
}

6. assert! 매크로

fn main() {
    let x = 5;
    assert!(x > 10, "x must be greater than 10, got {}", x);
    // thread 'main' panicked at 'x must be greater than 10, got 5'

    assert_eq!(x, 5);   // x == 5이면 통과
    assert_ne!(x, 10);  // x != 10이면 통과
}

panic!을 쓸 때와 쓰지 말 때

써야 하는 상황

1. 불변식(invariant) 위반

fn add_block(&mut self, block: Block) {
    // 이 조건이 깨지면 버그 — 프로그램 자체가 잘못된 것
    assert!(
        block.index == self.blocks.len() as u64,
        "Block index mismatch: expected {}, got {}",
        self.blocks.len(),
        block.index
    );
    self.blocks.push(block);
}

2. 테스트 코드

#[cfg(test)]
mod tests {
    #[test]
    fn test_block_hash() {
        let block = Block::genesis();
        assert!(!block.hash.is_empty(), "Genesis block must have a hash");
        assert_eq!(block.index, 0, "Genesis block index must be 0");
    }
}

3. 학습 중 임시 중단 코드

todo!, unimplemented!, unreachable!는 실행 가능한 완성 코드가 아니라, 개발 중 일부러 프로그램을 중단시키는 매크로입니다. 최종 예제나 실습 코드에는 남기지 않습니다.

#[derive(Debug)]
struct Block {
    nonce: u64,
}

fn mine_block(difficulty: usize) -> Result<Block, String> {
    if difficulty > 6 {
        return Err(String::from("difficulty is too high for this demo"));
    }

    Ok(Block { nonce: 42 })
}

fn verify_signature(signature: &[u8]) -> Result<bool, String> {
    if signature.is_empty() {
        return Err(String::from("signature is empty"));
    }

    Ok(true)
}

fn main() {
    let block = mine_block(2).expect("mining should succeed in the demo");
    let verified = verify_signature(&[1, 2, 3]).expect("signature should be present");

    println!("mined block: {:?}, signature verified: {}", block, verified);
}

완성 코드에서는 위처럼 실제 구현이나 명시적 에러 반환을 사용합니다.

4. 외부 입력이 아닌, 프로그래머의 실수로만 발생할 수 있는 상황

// 컴파일러가 None이 불가능하다고 판단하지 못하지만,
// 로직상 절대 None이 될 수 없는 경우
let last = self.blocks.last().expect("Blockchain must have at least one block");

쓰지 말아야 하는 상황

외부 입력, 네트워크, 파일 등 예상 가능한 에러

// 나쁜 코드 — 외부 입력을 panic으로 처리
fn parse_block_height(s: &str) -> u64 {
    s.parse::<u64>().unwrap()  // 잘못된 입력이면 panic!
}

// 좋은 코드 — Result로 처리
fn parse_block_height(s: &str) -> Result<u64, std::num::ParseIntError> {
    s.parse::<u64>()
}

블록체인에서 panic!의 위험성

스마트 컨트랙트에서의 panic

Solana 온체인 프로그램에서 panic!이 발생하면:

  1. 트랜잭션이 즉시 실패
  2. 해당 트랜잭션의 상태 변경이 롤백됨
  3. 수수료는 차감됨 (가스는 소모됨)
  4. 온체인 로그에 에러 메시지가 남음
// Solana 프로그램에서 나쁜 패턴
pub fn process_transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    let balance = ctx.accounts.source.amount;
    // 잔액 부족 시 panic! — 잘못된 접근
    assert!(balance >= amount, "Insufficient balance");
    ctx.accounts.source.amount -= amount;
    ctx.accounts.destination.amount += amount;
}

// 좋은 패턴 — 에러를 반환
pub fn process_transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    let balance = ctx.accounts.source.amount;
    if balance < amount {
        return Err(ErrorCode::InsufficientFunds.into());
    }
    ctx.accounts.source.amount -= amount;
    ctx.accounts.destination.amount += amount;
    Ok(())
}

서버 프로그램에서의 panic

Tokio 비동기 런타임에서 태스크 내부의 panic!은:

  • 해당 태스크만 종료 (프로세스 전체가 죽지 않음)
  • JoinHandle에서 에러로 처리 가능
  • 하지만 예기치 않은 상태 불일치를 만들 수 있음
// 프로덕션 서버에서 — panic을 잡아서 처리
use std::panic;

let result = panic::catch_unwind(|| {
    // panic이 날 수 있는 코드
    risky_operation()
});

match result {
    Ok(val) => println!("Success: {:?}", val),
    Err(_)  => eprintln!("Caught a panic!"),
}

Cargo.toml에서 panic 동작 설정

[profile.release]
# 릴리스 빌드에서 panic 시 즉시 abort (스택 unwinding 없음)
# 바이너리 크기 감소, 더 빠름
# 스마트 컨트랙트에서 선호
panic = "abort"

[profile.dev]
# 개발 빌드에서는 unwind (기본값) — 에러 메시지 풍부
panic = "unwind"

RUST_BACKTRACE 환경변수

panic 발생 시 스택 트레이스를 보려면:

RUST_BACKTRACE=1 cargo run
# 또는 전체 트레이스
RUST_BACKTRACE=full cargo run

출력 예시:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 10', src/main.rs:3:20
stack backtrace:
   0: rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::slice::index_failed
   3: my_project::main
             at ./src/main.rs:3:20

todo!, unimplemented!, unreachable! 비교

매크로의미사용 시점
todo!()아직 구현하지 않은 코드개발 중, 나중에 구현 예정
unimplemented!()의도적으로 구현하지 않음트레이트 메서드 중 일부만 구현
unreachable!()도달할 수 없는 코드로직상 불가능한 분기
#![allow(unused)]
fn main() {
enum Direction { North, South, East, West }

fn turn_left(dir: Direction) -> Direction {
    match dir {
        Direction::North => Direction::West,
        Direction::West  => Direction::South,
        Direction::South => Direction::East,
        Direction::East  => Direction::North,
    }
}

fn handle_special_only(dir: Direction) {
    match dir {
        Direction::North => println!("Special north handling"),
        _ => unreachable!("Only North should reach here"),
    }
}
}

요약

  • panic!: 복구 불가능한 에러 — 프로그램 즉시 종료
  • 써야 할 때: 버그, 불변식 위반, 테스트, todo/unimplemented
  • 쓰지 말아야 할 때: 외부 입력, 네트워크, 파일 등 예상 가능한 에러
  • 블록체인 스마트 컨트랙트에서 panic!은 트랜잭션 실패 + 가스 소모
  • 프로덕션 코드에서는 Result<T, E>를 사용

다음 챕터에서 Result<T, E>로 에러를 우아하게 처리하는 방법을 배웁니다.