Rust는 type, lifetimes, bounds check 등을 통해 메모리 오류와 data races를 자동으로 방지하지만 이러한 안전성에는 한계가 있다. 안전하다고 판단할 수 없는 유용한 기술들이 존재하고, 이를 위해 Rust는 Unsafe code를 제공한다.

Unsafe code

Unsafe code는 Rust에게 "내가 안전성을 보장할 수 없는 기능을 사용하겠다”는 의사를 전달하는 방법이다. 이를 통해 아래와 같은 기능을 사용할 수 있다.

Rust의 다른 안전성 검사(type, lifetimes, bounds check)는 정상적으로 적용된다. unsafe code는 단지 추가 기능을 사용할 수 있게 해줄 뿐이다. 이는 Rust가 C/C++ 처럼 자신의 기본 기능을 구현할 수 있는 능력을 획득하게 해준다.

Unsafe from What?

$ cat [crash.rs](<http://crash.rs/>)
fn main() {
		let mut a: usize = 0;
		let ptr = &mut a as *mut usize;
		
		unsafe {
				*ptr.offset(3) = 0x7ffff72f484c
		}
}

$ cargo build
Compiling unsafe-samples v0.1.0
Finished debug [unoptimized + debuginfo] target(s) in 0.44s
$ ../../target/debug/crash
crash: Error: .netrc file is readable by others.
crash: Remove password or make file unreadable by others.
Segmentation fault (core dumped)
$

이 프로그램은 로컬 변수 a에 대한 가변 참조를 빌려와서 이를 *mut usize 유형의 raw pointer로 변환하고, 메모리에서 세 개의 단어만큼 더 이동한 위치의 포인터를 생성한다. 이 위치는 main의 return주소가 저장된 곳이므로 프로그램은 반환 주소를 상수로 덮어쓰게 된다. 따라서 main에서 return할 때 예상치 못한 방식으로 동작하게 된다. 이러한 충돌은 unsafe 기능의 잘못된 사용, 특히 raw pointer의 역참조로 인해 발생한다.

Unsafe는 Rust가 자동으로 강제할 수 없는 규칙이지만 정의되지 않은 동작을 피하기 위해 반드시 따라야하는 계약을 부과한다. 계약은 type 및 lifetimes 검사 등의 내용을 포함하며, 특정 unsafe 기능에 대한 추가 규칙을 부과한다. 일반적으로 Rust는 이러한 계약에 대해 전혀 알지 못한다.

예를 들어, raw pointer 타입은 원래 참조의 끝을 넘어 이동한 포인터를 역참조하는 것을 금지되어있다. 위의 코드에서 *ptr.offset(3) = ... 표현식은 이 계약을 위반하지만 Rust는 코드를 문제 없이 컴파일한다. 따라서 unsafe를 사용할 때는 프로그래머가 코드가 계약을 준수하는지 확인할 책임을 진다.

unsafe 기능은 오직 unsafe 블록이나 unsafe 함수 내에서만 사용할 수 있다. unsafe 기능을 무의식적으로 사용하는 것을 어렵게 함으로써 Rust는 작성한 코드가 추가 규칙을 따르고 있는지 인지하게 만다.

Unsafe Block