Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

책의 여러 곳에서 구조체나 열거형 정의에 적용할 수 있는 derive 애트리뷰트를 다뤘습니다. derive 애트리뷰트는 derive 문법으로 표시된 타입에 대해, 기본 구현을 가진 트레이트 구현 코드를 자동으로 생성합니다.

이 부록에서는 derive 와 함께 사용할 수 있는 표준 라이브러리의 모든 트레이트를 참고용으로 정리합니다. 각 절에서는 다음 내용을 다룹니다.

  • 이 트레이트를 파생하면 어떤 연산자와 메서드를 쓸 수 있게 되는지
  • derive 가 제공하는 트레이트 구현이 실제로 무엇을 하는지
  • 그 트레이트를 구현한다는 것이 타입에 대해 무엇을 의미하는지
  • 그 트레이트를 구현해도 되는 조건과 구현하면 안 되는 조건
  • 그 트레이트가 필요한 연산의 예

derive 가 제공하는 기본 동작과 다른 동작이 필요하다면, 각 트레이트를 수동으로 구현하는 방법에 대한 자세한 내용은 표준 라이브러리 문서 를 참고하세요.

여기에 나열된 트레이트는 표준 라이브러리에서 정의된 것 중, derive 를 사용해 여러분의 타입에 구현할 수 있는 유일한 트레이트들입니다. 표준 라이브러리의 다른 트레이트들은 그럴듯한 기본 동작을 갖기 어렵기 때문에, 여러분이 만들고자 하는 것에 맞게 직접 구현해야 합니다.

파생할 수 없는 트레이트의 예로는 최종 사용자용 포매팅을 담당하는 Display 가 있습니다. 타입을 최종 사용자에게 어떻게 보여 줄지 늘 고민해야 합니다. 타입의 어떤 부분을 최종 사용자에게 보여 줄 수 있을까요? 그들이 관련 있다고 느낄 부분은 무엇일까요? 어떤 데이터 형식이 가장 적절할까요? 러스트 컴파일러는 이런 맥락을 알지 못하므로, 적절한 기본 동작을 대신 제공할 수 없습니다.

이 부록의 파생 가능 트레이트 목록이 전부를 뜻하는 것은 아닙니다. 라이브러리는 자신만의 트레이트에 대해서도 derive 를 구현할 수 있으므로, derive 와 함께 쓸 수 있는 트레이트 목록은 사실상 열려 있습니다. derive 구현에는 프로시저 매크로가 사용되며, 이는 20장의 “커스텀 derive 매크로” 절에서 다룹니다.

프로그래머 출력을 위한 Debug

Debug 트레이트는 포맷 문자열 안에서 디버그 포매팅을 가능하게 하며, {} 자리표시자 안에 :? 를 넣어 사용합니다.

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

예를 들어 assert_eq! 매크로를 사용하려면 Debug 트레이트가 필요합니다. 이 매크로는 같음 비교가 실패할 경우 인수로 전달된 인스턴스들의 값을 출력해, 두 인스턴스가 왜 같지 않았는지 프로그래머가 알 수 있게 해 줍니다.

동등성 비교를 위한 PartialEqEq

PartialEq 트레이트는 타입 인스턴스끼리의 동등성을 비교할 수 있게 해 주며, ==!= 연산자를 사용할 수 있게 합니다.

PartialEq 를 파생하면 eq 메서드가 구현됩니다. 구조체에 PartialEq 를 파생하면, 모든 필드가 같을 때만 두 인스턴스가 같고, 어느 하나라도 필드가 다르면 두 인스턴스는 같지 않다고 판단합니다. 열거형에 파생하면 각 variant 는 자기 자신과만 같고 다른 variant 와는 같지 않습니다.

예를 들어 assert_eq! 매크로를 쓰려면 PartialEq 트레이트가 필요합니다. 이 매크로는 두 타입 인스턴스가 같은지 비교할 수 있어야 하기 때문입니다.

Eq 트레이트는 메서드를 갖지 않습니다. 목적은 “표시된 타입의 모든 값은 자기 자신과 같다”는 사실을 나타내는 것입니다. EqPartialEq 를 구현한 타입에만 적용할 수 있지만, PartialEq 를 구현한 모든 타입이 Eq 를 구현할 수 있는 것은 아닙니다. 대표적인 예가 부동소수점 타입입니다. 부동소수점의 정의에 따르면, 숫자가 아님을 나타내는 NaN 값 두 개는 서로 같지 않습니다.

Eq 가 필요한 경우의 예로는 HashMap<K, V> 의 키가 있습니다. HashMap<K, V> 가 두 키가 같은지 판단할 수 있어야 하기 때문입니다.

순서 비교를 위한 PartialOrdOrd

PartialOrd 트레이트는 타입 인스턴스를 정렬 목적에 맞게 비교할 수 있게 합니다. PartialOrd 를 구현한 타입은 <, >, <=, >= 연산자와 함께 사용할 수 있습니다. PartialOrdPartialEq 도 구현한 타입에만 적용할 수 있습니다.

PartialOrd 를 파생하면 partial_cmp 메서드가 구현되며, 이는 Option<Ordering> 을 반환합니다. 주어진 두 값이 순서를 만들 수 없으면 None 이 됩니다. 대부분의 값은 비교 가능하지만 순서를 만들 수 없는 값의 예로는 부동소수점 NaN 이 있습니다. 어떤 부동소수점 수와 NaN 에 대해 partial_cmp 를 호출하면 None 이 반환됩니다.

구조체에 파생한 경우 PartialOrd 는 구조체 정의에 필드가 나타나는 순서대로 각 필드의 값을 비교합니다. 열거형에 파생한 경우에는, 열거형 정의에서 더 먼저 선언된 variant 가 더 뒤에 선언된 variant 보다 작다고 간주됩니다.

예를 들어 rand 크레이트의 gen_range 메서드는 범위 표현식으로 지정한 범위 안의 임의 값을 만들기 때문에 PartialOrd 트레이트가 필요합니다.

Ord 트레이트는 표시된 타입의 임의의 두 값에 대해 언제나 유효한 순서가 존재함을 알려 줍니다. Ord 트레이트는 cmp 메서드를 구현하는데, 유효한 순서가 항상 존재하므로 Option<Ordering> 이 아니라 Ordering 을 반환합니다. OrdPartialOrdEq 를 모두 구현한 타입에만 적용할 수 있습니다(EqPartialEq 를 요구합니다). 구조체와 열거형에 파생한 경우, cmpPartialOrd 의 파생 구현에서 partial_cmp 가 하는 것과 같은 방식으로 동작합니다.

Ord 가 필요한 예로는 값의 정렬 순서에 기반해 데이터를 저장하는 자료구조인 BTreeSet<T> 에 값을 저장할 때가 있습니다.

값 복제를 위한 CloneCopy

Clone 트레이트는 값의 깊은 복사를 명시적으로 만들 수 있게 하며, 이 복제 과정에는 임의의 코드 실행이나 힙 데이터 복사가 포함될 수 있습니다. Clone 에 대한 자세한 내용은 4장의 “변수와 데이터가 Clone 과 상호작용하는 방식” 절을 참고하세요.

Clone 을 파생하면 clone 메서드가 구현되는데, 타입 전체에 대해 구현할 때 타입의 각 부분에 대해 clone 을 호출합니다. 즉, 타입의 모든 필드 또는 값이 Clone 을 구현하고 있어야 Clone 을 파생할 수 있습니다.

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

Copy 트레이트는 스택에 저장된 비트만 복사해서 값을 복제할 수 있게 합니다. 임의의 코드를 실행할 필요가 없습니다. Copy 에 대한 더 자세한 설명은 4장의 “스택에만 있는 데이터: Copy 절을 참고하세요.

Copy 트레이트는 어떤 메서드도 정의하지 않습니다. 프로그래머가 메서드를 오버로드해 “임의의 코드가 실행되지 않는다”는 가정을 깨뜨리지 못하게 하기 위함입니다. 덕분에 모든 프로그래머는 값 복사가 매우 빠르다고 가정할 수 있습니다.

모든 부분이 Copy 를 구현하는 타입이라면 어떤 타입에도 Copy 를 파생할 수 있습니다. Copy 를 구현하는 타입은 반드시 Clone 도 구현해야 합니다. Copy 를 구현한 타입의 Clone 구현은 Copy 와 동일한 작업을 하는 아주 단순한 구현이기 때문입니다.

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() 를 사용해 나머지 필드에는 기본값을 넣을 수 있습니다.

예를 들어 Option<T> 인스턴스에 unwrap_or_default 메서드를 사용할 때 Default 트레이트가 필요합니다. Option<T>None 이라면, unwrap_or_defaultOption<T> 안에 들어 있던 타입 T 에 대해 Default::default 의 결과를 반환합니다.