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

15. no_std: 표준 라이브러리 없는 Rust 🔴

학습 목표:

  • OS가 없는 베어메탈(Bare-metal)이나 임베디드 타겟을 위해 표준 라이브러리를 제외하고 Rust를 작성하는 법을 배웁니다.
  • **core**와 alloc 크레이트의 근본적인 차이를 이해합니다.
  • 패닉 핸들러(Panic Handler) 설정 및 임베디드 C 개발 환경과의 결정적인 차이점을 익힙니다.
  • 하드웨어 추상화 계층(HAL)을 타입 시스템으로 안전하게 다루는 법을 살펴봅니다.

1. no_std란 무엇인가?

임베디드 C 개발자가 libc 없이 최소한의 런타임만 사용하는 것처럼, Rust에서도 #![no_std] 속성을 사용하면 std 라이브러리에 대한 의존성을 제거할 수 있습니다. 대신 모든 로직은 플랫폼 독립적인 core 라이브러리를 기반으로 동작합니다.

주요 라이브러리 계층 구분

레이어제공 기능OS / 힙(Heap) 필요 여부
core기본 타입, Option, Result, 반복자, 수학 연산 등불필요 (베어메탈용)
allocVec, String, Box, Arc 등 동적 할당 컬렉션OS 불필요, 할당자(Allocator) 필요
std파일 I/O, 네트워크, 스레드, HashMap필요 (OS 의존적)

2. 베어메탈 프로젝트 설정

OS가 제공하는 기본 기능이 없으므로, 패닉 발생 시의 행동과 프로그램 진입점(main)을 직접 정의해야 합니다.

// src/main.rs
#![no_std]   // 표준 라이브러리 사용 안 함
#![no_main]  // 표준 main 진입점 사용 안 함 (벡터 테이블에서 직접 호출)

use core::panic::PanicInfo;

// 패닉 발생 시 호출될 핸들러 정의 (필수)
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    // 실제 임베디드 장치에서는 시스템 리셋이나 LED 깜박임 등을 수행합니다.
    loop {} 
}

// 특정 하드웨어 전용 진입점 (예: Cortex-M)
// #[entry]
// fn main() -> ! { loop {} }

3. std 기능의 임베디드용 대안

표준 라이브러리가 없어도 강력한 커뮤니티 크레이트들이 그 빈자리를 채워줍니다.

사용 불가 기능임베디드 대안특징
println!defmt / rtt-target효율적인 로깅 (RTT/ITM 방식)
Vec, Stringheapless::Vec, String스택(Stack) 기반 고정 용량 컬렉션
HashMapheapless::FnvIndexMap할당자 없이 사용 가능한 해시맵
malloc (Heap)embedded-alloc힙 메모리가 꼭 필요할 때만 선별적 사용

💡 임베디드 C vs Rust: HAL 설계의 차이

C언어의 벤더 HAL은 레지스터 설정 실수나 동시성 제어 실패에 취약합니다. Rust는 이를 타입 시스템소유권으로 해결합니다.

// Rust 임베디드 코드 예시
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap(); // 싱글톤으로 주변장치 독점권 획득
    let gpioa = dp.GPIOA.split();             // GPIO 핀들 소유권 분할
    
    // 이 핀은 이제 '출력 모드인 PA5'라는 고유 타입을 가짐
    let mut led = gpioa.pa5.into_push_pull_output(); 
    
    loop {
        led.set_high().unwrap(); // 타입이 보장된 안전한 동작만 허용됨
        delay.delay_ms(500);
    }
}
  1. 싱글톤 보장: take()를 통해 동일 주변장치를 두 번 초기화하는 실수를 원천 차단합니다.
  2. 소유권 강제: 출력 핀으로 설정된 자원을 다른 함수에서 실수로 입력 핀으로 오인해 사용하는 것을 컴파일 타임에 막습니다.
  3. 데이터 경합 방지: 인터럽트 핸들러와 메인 루프 간의 데이터 공유를 빌림 검사기가 엄격히 검증합니다.

📝 실습 연습: no_std 링 버퍼 구현

할당자 없이 core 기능만 사용하여 고정 크기 **링 버퍼(Ring Buffer)**를 구현해 보세요.

  • 요구 사항:
    • const N: usize를 사용해 컴파일 타임에 크기를 결정합니다.
    • MaybeUninit<T>를 사용하여 초기화되지 않은 메모리를 안전하게 관리합니다.
    • 버퍼가 가득 찼을 때의 처리와 push/pop 메서드를 구현하세요.

📌 요약

  • **#![no_std]**는 OS 없는 환경을 위한 Rust의 필수 문취입니다.
  • **core**는 플랫폼 독립적이며 어디서든 사용 가능합니다.
  • heapless 크레이트는 동적 할당 없이도 강력한 데이터 구조를 제공합니다.
  • Rust의 임베디드 개발은 "런타임 에러를 컴파일 타임 에러로 옮기는 과정"입니다.