안전하지 않은 것으로 간주되지 않는 동작

안전하지 않은 것으로 간주되지 않는 동작 (Behavior not considered unsafe)

Rust 컴파일러는 다음 동작들을 안전하지 않은 것으로 취급하지 않아요. 다만 프로그래머 입장에서는 분명 바람직하지 않거나, 예상 밖이거나, 잘못된 동작일 수 있죠. 그래서 '안전하지 않음(unsafe)'의 기준과는 별개로, 이런 동작들이 실제로 어떤 의미를 가지는지 짚고 넘어갈게요.

출처: Rust Reference

본문

교착 상태 (Deadlocks)

여러 스레드가 서로가 가진 자원을 기다리며 영원히 멈춰 있는 상태를 말해요. 컴파일러는 이를 안전하지 않은 동작으로 취급하지 않아요.

메모리 및 기타 자원의 누수 (Leaks of memory and other resources)

프로그램이 종료될 때까지 자원을 해제하지 않는 현상이에요. 안전하지 않은 동작으로 분류되지는 않지만, 리소스 관리 관점에서는 분명히 문제가 될 수 있는 부분이죠.

소멸자를 호출하지 않고 종료하기 (Exiting without calling destructors)

프로그램이나 스코프를 벗어날 때 소멸자(destructor)가 호출되지 않은 채 종료되는 경우예요.

포인터 누수를 통한 무작위화된 베이스 주소 노출 (Exposing randomized base addresses through pointer leaks)

주소 공간 배치 무작위화(ASLR) 같은 기법으로 무작위화된 메모리 주소가 포인터를 통해 외부로 새어 나가는 상황이에요.

정수 오버플로 (Integer overflow)

프로그램이 산술 연산에서 오버플로를 일으킨다면, 그것은 프로그래머가 실수를 저질렀다는 뜻이에요. 여기서 중요한 구분이 하나 있어요. **산술 오버플로(arithmetic overflow)**와 **감싸기 산술(wrapping arithmetic)**은 서로 다른 개념이에요. 전자는 잘못된 것이고, 후자는 의도된 동작이죠.

프로그래머가 debug_assert! 어서션을 활성화했을 때(예를 들어 최적화하지 않은 빌드를 켰을 때) 구현체는 오버플로가 발생하면 패닉을 일으키는 동적 검사를 반드시 넣어야 해요. 그 외 방식의 빌드에서는 패닉이 나거나, 구현체 재량으로 값이 조용히 감싸질(wrapped) 수 있어요.

암시적으로 감싸진 오버플로의 경우, 구현체는 2의 보수(two's complement) 오버플로 규약을 사용해 잘 정의된(여전히 잘못된 것으로 간주되지만) 결과를 제공해야 해요.

정수 타입은 프로그래머가 명시적으로 감싸기 산술을 수행할 수 있게 고유 메서드(inherent method)를 제공해요. 예를 들어 i32::wrapping_add는 2의 보수 감싸기 덧셈을 수행하죠.

표준 라이브러리는 또한 Wrapping<T> 뉴타입을 제공해서, T의 모든 표준 산술 연산이 감싸기 의미론을 가지게 할 수 있어요.

오류 조건, 근거, 정수 오버플로에 대한 더 자세한 내용은 RFC 560을 참고하세요.

논리 오류 (Logic errors)

안전한 코드는 컴파일 타임에도 런타임에도 검사할 수 없는 추가적인 논리적 제약을 부과할 수 있어요. 프로그램이 그런 제약을 위반하면, 동작은 **불특정(unspecified)**일 수 있지만 정의되지 않은 동작(undefined behavior)은 아니에요. 여기에는 패닉, 잘못된 결과, 중단(abort), 종료되지 않음(non-termination)이 포함될 수 있어요. 동작이 실행할 때마다, 빌드에 따라, 또는 빌드 종류에 따라 달라질 수도 있죠.

예를 들어 HashEq를 함께 구현하려면, 같다고 간주되는 값들이 같은 해시를 가져야 해요. 또 다른 예로는 BinaryHeap, BTreeMap, BTreeSet, HashMap, HashSet 같은 자료 구조가 있는데, 이들은 데이터 구조에 들어 있는 동안 key를 수정하는 데 대한 제약을 설명해요. 이런 제약을 위반하는 것은 안전하지 않은 것으로 간주되지 않지만, 프로그램은 잘못된 것으로 간주되고 동작을 예측할 수 없게 돼요.

더 알아보기 (Learn more)