안전하지 않은 러스트(Unsafe Rust)는 러스트의 안전한 하위 집합을 확장하여 컴파일러가 소유권, 생명주기 및 별칭의 정확성을 확인할 수 없는 작업을 사용하는 것을 허용합니다. 주요 용도: 저수준 라이브러리와의 상호작용, FFI, 수동 메모리 관리, 안전한 러스트 모델에 맞지 않는 추상화 구현.
안전하지 않은 러스트의 주요 특징:
unsafe { ... } 또는 함수: unsafe fn some_func()예시:
let x: i32 = 10; let ptr: *const i32 = &x as *const i32; unsafe { println!("값: {}", *ptr); // raw pointer 역참조 }
안전하지 않은 블록 내의 코드는 완전히 안전하지 않고, 컴파일러에 의해 검사되지 않는가요, 아니면 컴파일러가 여전히 borrow checker 및 기타 검사를 적용하나요?
답변: 아닙니다, 안전하지 않은 블록 내에서도 컴파일러는 러스트의 여러 규칙(예: 타입, 소유권 규칙, 문법)을 계속 검토하지만, 그렇지 않은 경우에는 허용되지 않는 작업만 수행할 수 있습니다. borrow checker를 완전히 비활성화할 수는 없습니다!
예시:
let mut x = 0; let r1 = &mut x as *mut i32; // 금지: let r2 = &mut x as *mut i32; // 안전하지 않은 영역 내에서도 변경 가능성을 위반하면 오류가 발생합니다.
이야기
비동기 파일 라이브러리에서 raw pointer를 사용하여 버퍼를 제어했으나, 버퍼의 생명주기를 올바르게 추적하지 못했습니다. 결과적으로 파일을 닫을 때 pointer가 "유령"이 되어 access가 정의되지 않은 동작을 초래했습니다 (unsafe 섹션이 use-after-free로부터 구해주지 않았습니다).
이야기
자체 Vec-like 구조체를 구현한 프로그래머가 unsafe를 통해 버퍼를 수동으로 확장했습니다. 오프셋 오류가 발생하여 요소의 잘못된 복사와 데이터 손상이 발생했습니다, alignment와 타입의 layout이 고려되지 않았기 때문입니다.
이야기
스레드 설명자에서 적절한 동기화 없이 정적 가변 포인터(static mut)가 사용되었습니다. 이로 인해 멀티 스레드 모드에서 우연히 데이터 레이스가 발생하여 sporadic crash가 일어나며, 이는 fuzzing을 통해서만 잡을 수 있었습니다.