신뢰성
함수는 인수를 검증합니다 (C-VALIDATE)
Rust API는 일반적으로 “보낼 때는 보수적으로, 받을 때는 관대하게“라는 견고성 원칙을 따르지 않습니다.
대신 Rust 코드는 실용적으로 가능한 경우 언제나 입력의 유효성을 강제해야 합니다.
강제는 다음과 같은 메커니즘을 통해 달성할 수 있습니다(선호하는 순서대로 나열합니다).
정적 강제
잘못된 입력을 배제하는 인수 타입을 선택하십시오.
예를 들어, 다음을
#![allow(unused)]
fn main() {
fn foo(a: Ascii) { /* ... */ }
}
아래보다 선호하십시오.
#![allow(unused)]
fn main() {
fn foo(a: u8) { /* ... */ }
}
여기서 Ascii는 최상위 비트가 0임을 보장하는 u8의 래퍼입니다. 타입 안전한 래퍼를 만드는 방법에 대한 자세한 내용은 newtype 패턴(C-NEWTYPE)을 참고하십시오.
정적 강제는 보통 런타임 비용이 거의 들지 않습니다. 비용을 경계 지점으로 밀어내기 때문입니다(예: u8이 처음 Ascii로 변환될 때). 또한 런타임 실패를 통해서가 아니라 컴파일 중에 버그를 일찍 잡아냅니다.
반면에 어떤 속성은 타입으로 표현하기 어렵거나 불가능합니다.
동적 강제
입력을 처리하면서(또는 필요하다면 미리) 입력을 검증하십시오. 동적 검사는 정적 검사보다 구현하기 쉬운 경우가 많지만, 몇 가지 단점이 있습니다:
- 런타임 오버헤드(입력 처리의 일부로 검사를 수행할 수 있는 경우는 제외합니다).
- 버그 발견이 늦어집니다.
panic!또는Result/Option타입을 통해 실패 사례가 생기며, 클라이언트 코드는 이를 처리해야 합니다.
debug_assert!를 사용한 동적 강제
동적 강제와 같지만, 프로덕션 빌드에서 비용이 큰 검사를 쉽게 끌 수 있습니다.
옵트아웃이 가능한 동적 강제
동적 강제와 같지만, 검사를 생략하는 형제 함수를 추가합니다.
관례적으로 이러한 옵트아웃 함수에는 _unchecked 같은 접미사를 붙이거나 raw 하위 모듈에 둡니다.
검사하지 않는 함수는 (1) 성능상 검사를 피해야 하고 (2) 클라이언트가 다른 방법으로 입력이 유효하다고 확신할 수 있는 경우에 신중하게 사용할 수 있습니다.
소멸자는 절대 실패하지 않습니다 (C-DTOR-FAIL)
소멸자는 패닉 중에도 실행되며, 그 상황에서 소멸자가 실패하면 프로그램이 중단(abort)됩니다.
소멸자에서 실패하는 대신, 깔끔한 정리 여부를 확인하는 별도의 메서드(예: close 메서드)를 제공하고 이 메서드가 문제를 알리는 Result를 반환하도록 하십시오. 그 close 메서드가 호출되지 않으면 Drop 구현이 정리를 수행해야 하며, 이때 발생하는 오류는 무시하거나 로그/트레이스로 남겨야 합니다.
블로킹될 수 있는 소멸자에는 대안이 있습니다 (C-DTOR-BLOCK)
마찬가지로 소멸자는 블로킹 작업을 호출해서는 안 됩니다. 블로킹 작업은 디버깅을 훨씬 더 어렵게 만들 수 있습니다. 이번에도 실패하지 않고 블로킹되지 않는 해체를 준비하는 별도의 메서드를 제공하는 것을 고려하십시오.