부록 C: 파생 가능한 트레이트

이 책의 여러 곳에서 구조체나 열거형 정의에 적용할 수 있는 derive 속성에 대해 설명했습니다. derive 속성은 derive 문법으로 명시한 타입에 대해 자체적인 기본 구현이 있는 트레이트를 구현하는 코드를 생성합니다.

이 부록에서는 derive와 함께 사용할 수 있는 표준 라이브러리의 모든 트레이트에 대한 참고 자료를 제공합니다. 각 절이 다룰 내용은 다음과 같습니다:

  • 이 트레이트를 파생하면 어떤 연산자와 메서드가 활성화되는지
  • derive가 제공하는 트레이트의 구현체가 하는 일
  • 타입에 대해 트레이트를 구현한다는 것의 의미
  • 트레이트를 구현할 수 있는 조건 혹은 허용되지 않는 조건
  • 트레이트가 필요한 연산들의 예

derive 속성이 제공하는 것과는 다른 동작을 원한다면, 각 트레이트에 대한 표준 라이브러리 문서를 참고하여 수동으로 구현하는 방법에 대한 자세한 내용을 확인하세요.

여기에 나열된 트레이트들은 표준 라이브러리에서 derive를 사용하여 타입에 구현할 수 있는 유일한 트레이트들입니다. 표준 라이브러리에 정의된 다른 트레이트에는 적절한 기본 동작이 없으므로, 달성하려는 목적에 적합한 방식으로 구현하는 것은 여러분의 몫입니다.

파생될 수 없는 트레이트의 예로는 Display가 있는데, 이는 최종 사용자를 위한 서식을 처리합니다. 여러분은 최종 사용자에게 타입을 표시할 적절한 방법을 항상 고려해야 합니다. 최종 사용자에게 어떤 부분을 표시해야 할까요? 어떤 부분이 관련성이 있을까요? 어떤 형식의 데이터가 가장 관련성이 높을까요? 러스트 컴파일러에는 이러한 인사이트가 없으므로 적절한 기본 동작을 제공할 수 없습니다.

이 부록에 나열된 트레이트가 파생 가능한 트레이트를 전부 포괄하지는 않습니다: 라이브러리는 자신의 트레이트에 대해 대한 derive를 구현할 수 있으므로, derive를 사용할 수 있는 트레이트 목록은 정말 개방적입니다. derive를 구현하려면 절차적 매크로를 사용해야 하며, 이는 19장의 ‘매크로’절에서 다룹니다.

프로그래머 출력을 위한 Debug

Debug 트레이트는 형식 문자열에서 디버그 서식을 활성화하는데, 이는 {} 자리표시자 내에 :?를 추가하여 표시합니다.

Debug 트레이트는 디버깅 목적으로 어떤 타입의 인스턴스를 출력할 수 있게 해주므로, 여러분과 여러분의 타입을 사용하는 다른 프로그래머들은 프로그램의 실행 중 특정 지점에서 인스턴스를 검사할 수 있습니다.

Debug 트레이트는 이를테면 assert_eq! 매크로를 사용할 때 필요합니다. 이 매크로는 동등 단언이 실패했을 경우 인스턴스의 값을 출력하여 프로그래머가 두 인스턴스가 같지 않은 이유를 확인할 수 있도록 해 줍니다.

동등 비교를 위한 PartialEqEq

PartialEq 트레이트는 타입의 인스턴스를 비교하여 동등 여부를 확인하고 ==!= 연산자를 사용할 수 있게 해 줍니다.

PartialEq를 파생시키면 eq 메서드가 구현됩니다. PartialEq가 구조체에 파생되면, 두 인스턴스는 모든 필드가 동일할 때만 동일하고, 필드 중 어느 하나라도 동일하지 않다면 두 인스턴스는 동일하지 않습니다. 열거형에 파생되면, 각 배리언트는 자기 자신과는 동일하고 다른 변형과는 동일하지 않습니다.

예를 들면 PartialEq 트레이트는 assert_eq! 매크로를 사용할 때 필요한데, 이 매크로는 두 인스턴스를 비교하여 동등 여부를 확인할 수 있어야 하기 때문입니다.

Eq 트레이트에는 메서드가 없습니다. 이 트레이트의 목적은 어노테이션된 타입의 모든 값에 대해 해당 값이 자기 자신과 동일하다는 것을 나타내는 것입니다. Eq 트레이트는 PartialEq를 구현한 모든 타입에 적용할 수 있지만, PartialEq를 구현한 모든 타입이 Eq를 구현할 수 있는 것은 아닙니다. 이에 대한 한 가지 예는 부동 소수점 숫자 타입입니다: 부동 소수점 숫자 구현은 not-a-number(NaN) 값의 두 인스턴스가 서로 동일하지 않다고 명시합니다.

Eq가 필요한 예시로는 HashMap<K, V>의 키로서, HashMap<K, V>가 두 키가 동일한지 여부를 알 수 있도록 하는 것입니다.

순서 비교를 위한 PartialOrdOrd

PartialOrd 트레이트는 정렬 목적을 위하여 타입의 인스턴스를 비교할 수 있게 해줍니다. PartialOrd를 구현한 타입은 <, >, <=, >= 연산자를 사용할 수 있습니다. PartialOrd 트레이트는 PartialEq를 구현한 타입에만 적용할 수 있습니다.

PartialOrd 트레이트를 파생시키면 partial_cmp 메서드가 구현되는데, 이는 Option<Ordering>을 반환하며, 반환 값은 주어진 값이 순서를 정의하지 않을 때 None이 됩니다. 이 트레이트를 구현한 타입의 대부분의 값은 비교할 수 있지만, 순서를 정의하지 않는 값의 예가 있다면 not-a-number (NaN) 부동 소수점 값입니다. NaN 부동 소수점 숫자와 어떤 부동 소수점 숫자를 사용하여 partial_cmp를 호출하면 None이 반환될 것입니다.

구조체에 대해 파생되면, PartialOrd는 구조체 정의에서 필드가 나타나는 순서대로 각 필드의 값을 비교하는 방식으로 두 인스턴스를 비교합니다. 열거형에 대해 파생되면, 열거형 정의에서 먼저 선언된 배리언트는 나중에 선언된 배리언트보다 작다고 간주됩니다.

예를 들면 PartialOrd 트레이트는 범위 표현식에 의해 지정된 범위 내에서 임의의 값을 생성하는 rand 크레이트의 gen_range 메서드를 사용할 때 필요합니다.

Ord 트레이트는 어떤 두 값에 대해 항상 유효한 순서가 존재한다는 것을 알려줍니다. Ord 트레이트는 cmp 메서드를 구현하는데, 이는 Option<Ordering> 이 아닌 Ordering을 반환합니다. 이는 항상 유효한 순서가 존재하기 때문입니다. Ord 트레이트는 PartialOrdEq를 구현한 타입에만 적용할 수 있습니다. (EqPartialEq를 필요로 합니다.) 구조체와 열거형에 대해 파생되면, cmpPartialOrdpartial_cmp에 대한 파생 구현체가 동작하는 방식과 동일하게 동작합니다.

Ord가 필요한 한 가지 예는 BTreeSet<T>에 값을 저장할 때인데, 이는 값의 정렬 순서에 따라 데이터를 저장하는 데이터 구조입니다.

값을 복제하기 위한 CloneCopy

Clone 트레이트는 명시적으로 값의 깊은 복사 (deep copy) 를 생성할 수 있게 해주며, 복제 과정은 임의의 코드를 실행하고 힙 데이터를 복사할 수도 있습니다. Clone에 대한 더 자세한 내용은 4장의 ‘변수와 데이터 간 상호작용 방식: 클론 (clone)’ 절을 참고하세요.

Clone을 파생하면 clone 메서드가 구현되는데, 이는 타입 전체에 대해 구현될 때 타입의 각 부분에 대해 clone을 호출합니다. 이는 Clone을 파생하기 위해서는 타입의 모든 필드나 값 또한 Clone을 구현해야 한다는 것을 의미합니다.

Clone이 필요한 경우의 한 예에는 슬라이스에 to_vec 메서드를 호출할 때가 있습니다. 슬라이스는 자신이 가진 타입 인스턴스를 소유하지 않지만, to_vec에서 반환된 벡터는 자신의 인스턴스를 소유해야 하므로, to_vec은 각 아이템에 대해 clone을 호출합니다. 따라서 슬라이스에 저장된 타입은 Clone을 구현해야 합니다.

Copy 트레이트는 임의의 코드 없이 스택에 저장된 비트만 복사하여 값을 복제할 수 있게 해 줍니다. Copy에 대한 더 자세한 내용은 4장의 ‘스택에만 저장되는 데이터: 복사 (copy)’절을 참고하세요.

Copy 트레이트에는 아무 메서드도 정의되어 있지 않은데, 이는 프로그래머가 메서드를 오버로딩하고 임의의 코드를 실행하지 않는다는 가정을 위반하는 것을 방지하기 위해서입니다. 따라서 모든 프로그래머는 복사가 매우 빠르게 수행될 것이라고 가정할 수 있습니다.

어떤 타입에 대해 Copy를 파생하려면 그 타입의 모든 부분이 Copy를 구현해야 합니다. Copy를 구현하는 타입은 또한 Clone을 구현해야 하는데, 왜냐하면 Copy를 구현하는 타입은 Copy와 동일한 작업을 수행하는 Clone의 단순한 구현체를 가지고 있기 때문입니다.

Copy 트레이트는 드물게만 요구됩니다; Copy를 구현하는 타입은 최적화가 가능하므로 clone을 호출할 필요가 없으며, 이는 코드를 더 간결하게 만듭니다.

Copy를 사용하여 가능한 모든 것은 Clone을 사용하여 수행할 수 있지만, 코드가 느려지거나 clone을 사용해야 하는 경우가 있을 수 있습니다.

어떤 값을 고정 크기의 값으로 매핑하기 위한 Hash

Hash 트레이트는 해시 함수를 사용하여 임의의 크기를 가진 타입의 인스턴스를 고정 크기의 값으로 매핑할 수 있게 해 줍니다. Hash를 파생하면 hash 메서드가 구현됩니다. 파생된 hash 메서드의 구현체는 타입의 각 부분에 대해 hash를 호출한 결과를 조합하는데, 이는 Hash를 파생하기 위해서는 타입의 모든 필드 혹은 값 또한 Hash를 구현해야 한다는 것을 의미합니다.

Hash를 필요로 하는 한 가지 예는 효율적으로 데이터를 저장하기 위하여 HashMap<K, V>에 키를 저장할 때입니다.

기본값을 위한 Default

Default 트레이트는 타입에 대한 기본값을 생성할 수 있게 해 줍니다. Default를 파생하면 default 함수가 구현됩니다. 파생된 default 함수의 구현체는 타입의 각 부분에 대해 default를 호출하는데, 이는 Default를 파생하기 위해서는 타입의 모든 필드 혹은 값 또한 Default를 구현해야 한다는 것을 의미합니다.

Default::default 함수는 일반적으로 5장의 ‘기존 인스턴스를 이용해 새 인스턴스를 만들 때 구조체 업데이트 문법 사용하기’절에서 다룬 구조체 업데이트 구문과 함께 사용됩니다. 구조체의 몇 개의 필드만 커스터마이징한 다음 나머지 필드에 대해 기본값을 설정하고 사용하려면 ..Default::default()를 사용할 수 있습니다.

Default 트레이트는 예를 들면 Option<T> 인스턴스에서 unwrap_or_default 메서드를 사용할 때 필요합니다. Option<T>None이면, unwrap_or_default 메서드는 Option<T>에 저장되는 T 타입에 대한 Default::default의 결과를 반환합니다.