unsafe 키워드
unsafe 키워드
unsafe 키워드는 어떤 안전함을 증명해야 할 의무를 만들거나, 혹은 그 의무를 이행했음을 선언할 때 쓰여요. 구체적으로 두 가지 역할이 있어요.
출처: Rust Reference
본문
첫째, 다른 곳에서 지켜져야 할 추가 안전 조건을 정의하는 코드를 표시해요. 여기에는 unsafe fn, unsafe static, unsafe trait가 해당돼요.
둘째, 프로그래머가 다른 곳에 정의된 안전 조건을 만족시켰다고 단언하는 코드를 표시해요. 여기에는 unsafe {}, unsafe impl, unsafe_op_in_unsafe_fn이 켜지지 않은 상태의 unsafe fn 본문, unsafe extern, 그리고 #[unsafe(attr)]가 해당돼요.
unsafe 키워드는 여러 문맥에서 나타날 수 있어요.
- unsafe 함수(
unsafe fn) - unsafe 블록(
unsafe {}) - unsafe 트레이트(
unsafe trait) - unsafe 트레이트 구현(
unsafe impl) - unsafe 외부 블록(
unsafe extern) - unsafe 외부 정적(
unsafe static) - unsafe 속성(
#[unsafe(attr)])
unsafe 함수 (unsafe fn)
unsafe 함수는 모든 문맥과 모든 가능한 입력에서 안전하지 않은 함수예요. 이 함수들은 **추가 안전 조건(extra safety conditions)**을 갖고 있어요. 이 조건은 모든 호출자가 반드시 지켜야 하는데, 컴파일러는 이를 검사하지 않아요. 예를 들어 get_unchecked는 인덱스가 범위 안에 있어야 한다는 추가 안전 조건을 가져요. unsafe 함수에는 그 추가 안전 조건이 무엇인지 설명하는 문서가 함께 있어야 해요.
이런 함수는 unsafe 키워드로 시작해야 하고, unsafe 블록 안에서만 (혹은 unsafe_op_in_unsafe_fn 린트가 없는 unsafe fn 내부에서만) 호출할 수 있어요.
unsafe 블록 (unsafe {})
코드 블록 앞에 unsafe 키워드를 붙이면, 그 블록 안에서 다른 unsafe 함수를 호출하거나 원시 포인터를 역참조하는 것 같은 **unsafe 동작(unsafe actions)**을 쓸 수 있어요.
기본적으로 unsafe 함수의 본문도 unsafe 블록으로 취급돼요. 이건 unsafe_op_in_unsafe_fn 린트를 켜면 바꿀 수 있어요.
연산을 unsafe 블록으로 감싼다는 것은, 프로그래머가 그 블록 안의 모든 연산의 추가 안전 조건을 만족시킬 것을 신경 썼다고 선언하는 거예요.
구조를 보면 unsafe 블록은 unsafe 함수의 **논리적 쌍(dual)**이에요. unsafe 함수가 호출자 반드시 지켜야 할 증명 의무를 정의한다면, unsafe 블록은 그 안에서 호출되는 함수·연산들의 관련 증명 의무를 모두 이행했음을 주장해요. 증명 의무를 이행하는 방법은 여러 가지예요. 런타임 검사나 어떤 속성이 확실하다는 것을 보장하는 자료구조 불변식이 있을 수도 있고, unsafe 블록이 unsafe fn 안에 있어 그 함수의 증명 의무를 이용할 수도 있어요.
unsafe 블록은 외부 라이브러리를 감싸거나, 하드웨어를 직접 다루거나, 언어에 없는 기능을 구현할 때 쓰여요. 예를 들어 Rust는 언어 안에서 메모리 안전한 동시성을 구현할 만한 기능은 제공하지만, 표준 라이브러리의 스레드와 메시지 전달 구현은 unsafe 블록을 사용해요.
Rust의 타입 시스템은 동적 안전 요구사항을 보수적으로 근사(approximation)하기 때문에, 가끔은 안전 코드를 쓸 때 성능 비용이 생겨요. 예를 들어 이중 연결 리스트는 트리 구조가 아니라서 안전 코드에서는 참조 카운트 포인터로만 표현할 수 있어요. unsafe 블록으로 역방향 링크를 원시 포인터로 표현하면 참조 카운팅 없이 구현할 수 있죠. (이 예시에 대한 더 깊은 탐구는 "Learn Rust With Entirely Too Many Linked Lists"를 참고하세요.)
unsafe 트레이트 (unsafe trait)
unsafe 트레이트는 구현자가 반드시 지켜야 할 추가 안전 조건을 가진 트레이트예요. unsafe 트레이트에는 그 추가 안전 조건이 무엇인지 설명하는 문서가 함께 있어야 해요.
이런 트레이트는 unsafe 키워드로 시작해야 하고 unsafe impl 블록으로만 구현할 수 있어요.
unsafe 트레이트 구현 (unsafe impl)
unsafe 트레이트를 구현할 때는 구현에 unsafe 키워드를 붙여야 해요. unsafe impl이라고 쓰면 프로그래머가 그 트레이트가 요구하는 추가 안전 조건을 만족시킬 것을 신경 썼다고 선언하는 거예요.
unsafe 트레이트 구현은 unsafe 트레이트의 논리적 쌍이에요. unsafe 트레이트가 구현자가 지켜야 할 증명 의무를 정의한다면, unsafe 구현은 그 관련 증명 의무를 모두 이행했음을 주장해요.
unsafe 외부 블록 (unsafe extern)
외부 블록을 선언하는 프로그래머는 그 안에 들어 있는 항목들의 시그니처가 올바르다는 것을 보장해야 해요. 그렇게 하지 못하면 정의되지 않은 동작(undefined behavior)으로 이어질 수 있어요. 그 의무를 충족했음을 나타내는 게 바로 unsafe extern이에요.
2024 에디션 차이점: 2024 에디션 이전에는 extern 블록을 안전한(unsafe 아님) 채로 허용했어요.
unsafe 속성 (#[unsafe(attr)])
unsafe 속성은 사용할 때 반드시 지켜야 할 추가 안전 조건이 있는 속성이에요. 컴파일러는 이 조건이 지켜졌는지 검사할 수 없어요. 이 조건을 지켰음을 단언하려면 이런 속성을 unsafe(..)로 감싸야 해요. 예를 들어 #[unsafe(no_mangle)]처럼요.
더 알아보기
- unsafe 동작의 종류를 자세히 나열하는 Unsafety 장을 함께 보면 어떤 연산이 unsafe 블록을 요구하는지 더 분명해져요.
unsafe_op_in_unsafe_fn린트와 2024 에디션의 unsafe extern 변경 사항을 살펴보면 최신 기준으로 코드를 다듬을 수 있어요.