■ 트레잇트레잇은 타입들이 공통적으로 갖는 동작에 대해 추상화하도록 해줍니다. 이는 Java의 Interface에서 추상화 함수를 정의함으로써 강제력을 제공하는것과 유사합니다. 트레잇을 제네릭 파라미터의 타입으로 사용하는 상황에서 트레잇 바운드를 통해 서로 다른 구조체에 연관성을 제공할 수 있습니다. Show ◆ 트레잇 구현Animal 트레잇 타입으로 구현한 Dog는 추상화된 custom_bark 메소드와 구현된 common_bark 메소드 두가지 메소드를 갖습니다. custom_bark 메소드는 Dog 구현부(impl)에서 강제적으로 정의가 되어야 하지만, common_bark 메소드는 정의하지 않고 Animal 트레잇 타입으로 구현한 모든 구조체에서 사용할 수 있습니다.
▶ 출력 결과Common Bark ◆ 트레잇 바운드트레잇 바운드는 서로 다른 구조체가 동일한 트레잇을 구현하고 있는 상황에서 어떤 함수에 제네릭의 타입을 트레잇으로 하고 있으면 동일한 트레잇을 구현하고 있는 서로 다른 구조체를 파라미터로 갖을 수 있습니다. 따라서 다양성의 특징을 보여줍니다.
▶ 출력 결과Dog Bark The Rust Programming Language트레잇: 공유 동작을 정의하기트레잇은 다른 종류의 추상화를 사용할 수 있도록 해줍니다: 이는 타입들이 공통적으로 갖는 동작에 대하여 추상화하도록 해줍니다. 트레잇(trait) 이란 러스트 컴파일러에게 특정한 타입이 갖고 다른 타입들과 함께 공유할 수도 있는 기능에 대해 말해줍니다. 우리가 제네릭 타입 파라미터를 사용하는 상황에서는, 컴파일 타임에 해당 제네릭 타입이 어떤 트레잇을 구현한 타입이어야 함을 명시하여, 그러한 상황에서 우리가 사용하길 원하는 동작을 갖도록 하기 위해 트레잇 바운드(trait bounds) 를 사용할 수 있습니다.
트레잇 정의하기어떤 타입의 동작은 우리가 해당 타입 상에서 호출할 수 있는 메소드들로 구성되어 있습니다. 만일 우리가 서로 다른 타입에 대해 모두 동일한 메소드를 호출할 수 있다면 이 타입들은 동일한 동작을 공유하는 것입니다. 트레잇의 정의는 어떠한 목적을 달성하기 위해 필요한 동작의 집합을 정의하기 위해 메소드 시그니처들을 함께 묶는 방법입니다. 예를 들면, 다양한 종류와 양의 텍스트를 갖는 여러 가지의 구조체를 가지고 있다고 칩시다: 우리는 Filename: lib.rs
Listing 10-11:
트레잇은 한 줄 당 하나의 메소드 시그니처와 각 줄의 끝에 세미콜론을 갖도록 함으로써, 본체 내에 여러 개의 메소드를 가질 수 있습니다. 특정 타입에 대한 트레잇 구현하기
Filename: lib.rs
Listing 10-12: 어떤 타입 상에서의 트레잇 구현은 트레잇과 관련이 없는 메소드를 구현하는 것과 유사합니다. 다른 점은 트레잇을 한번 구현했다면, 트레잇의 일부가 아닌 메소드들을 호출했던
것과 동일한 방식으로
이 코드는 Listing 10-12에서 Filename: lib.rs
Listing 10-13: 우리의 이 코드는 또한 트레잇 구현과 함께 기억할 한 가지 제한사항이 있습니다: 트레잇 혹은 타입이 우리의 크레이트 내의 것일 경우에만 해당 타입에서의 트레잇을 정의할 수 있습니다. 바꿔 말하면, 외부의 타입에 대한 외부 트레잇을 구현하는 것은 허용되지 않습니다. 예를 들어, 기본 구현종종 모든 타입 상에서의 모든 구현체가 커스텀 동작을 정의하도록 하는 대신, 트레잇의 몇몇 혹은 모든 메소드들에 대한 기본 동작을 갖추는 것이 유용할 수 있습니다. 특정한 타입에 대한 트레잇을 구현할 때, 각 메소드의 기본 동작을 유지하거나 오버라이드(override)하도록 선택할 수 있습니다. Listing 10-14는 우리가 Listing 10-11에서 한 것과 같이 메소드 시그니처를 정의만 하는 선택 대신 Filename: lib.rs
Listing
10-14: 만일 우리가 Listing 10-12에서 한 것과 같은 커스텀 구현을 정의하는 대신
비록
위의 코드는
기본 구현은 동일한 트레잇 내의 다른 메소드들을 호출하는 것이 허용되어 있는데, 심지어 그 다른 메소드들이 기본 구현을 갖고 있지 않아도 됩니다. 이러한 방식으로, 트레잇은 수많은 유용한 기능을 제공하면서도 다른 구현자들이 해당 트레잇의 작은 일부분만 구현하도록 요구할 수 있습니다. 우리는
이 버전의
일단
위의 코드는 오버라이딩된 구현으로부터 기본 구현을 호출하는 것은 불가능하다는 점을 기억해주세요. 트레잇 바운드이제 트레잇을 정의하고 어떤 타입들에 대해 이 트레잇을 구현해봤으니, 제네릭 타입 파라미터를 이용하는 트레잇을 사용할 수 있습니다. 우리는 제네릭 타입에 제약을 가하여 이 제네릭 타입이 어떠한 타입이든 되기 보다는, 이 제네릭 타입이 특정한 트레잇을 구현하여 이 타입들이 가지고 있을 필요가 있는 동작을 갖고 있도록 타입들로 제한함을 컴파일러가 확신하도록 할 수 있습니다. 예를 들면, Listing 10-12에서는
트레잇 바운드는 제네릭 타입 파라미터의 선언부와 함께, 꺾쇠 괄호 내에 콜론 뒤에 옵니다.
여러 개의 제네릭 타입 파라미터를 가진 함수들에 대하여, 각 제네릭은 고유의 트레잇 바운드를 가집니다. 함수 이름과 파라미터 리스트 사이의 꺾쇠 괄호 내에 많은 수의 트레잇 바운드 정보를 특정하는 것은 코드를 읽기 힘들게 만들 수 있으므로, 함수 시그니처 뒤에
함수 이름, 파라미터 리스트, 그리고 반환 타입이 서로 가까이 있도록 하여, 이쪽이 덜 어수선하고 이 함수의 시그니처를 많은 트레잇 바운드를 가지고 있지 않은 함수처럼 보이도록 만들어 줍니다. 트레잇 바운드를 사용하여 largest 함수 고치기따라서 여러분이 어떤 제네릭 상에서 어떤 트레잇으로 정의된 동작을 이용하기를 원하는 어떤 경우이든, 여러분은 해당 제네릭 타입 파라미터의 타입내에 트레잇 바운드를 명시할 필요가 있습니다. 이제 우리는 Listing 10-5에서 제네릭 타입 파라미터를 사용하는
이 코드를 컴파일하면, 다른 에러를 얻게 됩니다:
이 에러에 대한 열쇠는 만약 이 코드를 오직 Filename: src/main.rs
Listing 10-15: 만일 우리의 트레잇과 트레잇 바운드는 중복을 제거하기 위하여 제네릭 타입 파라미터를 사용하는 코드를 작성할 수 있도록 해주지만, 여전히 컴파일러에게 해당 제네릭 타입이 어떤 동작을 할 필요가 있는지를 정확히 명시하도록 해줍니다. 컴파일러에게 트레잇 바운드를 제공하기 때문에, 우리 코드와 함께 이용되는 모든 구체적인 타입들이 정확한 동작을 제공하는지를 확인할 수 있습니다. 동적 타입 언어에서는, 어떤 타입에 대해 어떤 메소드를 호출하는 시도를 했는데 해당 타입이 그 메소드를 구현하지 않았다면, 런타임에 에러를 얻게 됩니다. 러스트는 이러한 에러들을 컴파일 타임으로 옮겨서 우리의 코드가 실행 가능하기 전에 그 문제들을 해결하도록 우리를 강제합니다. 이에 더해서, 우리는 런타임에 해당 동작에 대한 검사를 하는 코드를 작성할 필요가 없는데, 우리는 이미 컴파일 타임에 이를 확인했기 때문이며, 이는 제네릭의 유연성을 포기하지 않고도 다른 언어들에 비해 성능을 향상시킵니다. 우리가 심지어 아직 알아채지도 못한 라이프타임(lifetime) 이라 불리는 또다른 종류의 제네릭이 있습니다. 라이프타임은 어떤 타임이 우리가 원하는 동작을 갖도록 확신하는데 도움을 주기 보다는, 참조자들이 우리가 원하는 만큼 오랫동안 유효한지를 확신하도록 도와줍니다. 라이프타임이 어떤 식으로 그렇게 하는지를 배워봅시다. |