연관 타입(associated type)
type Target;
과 같이 트레이트에서 자리표시자 타입을 명시하면, 트레이트 구현자는 구체적인 타입을 지정할 수 있다.
트레이트가 구현될 때까지 어떤 타입인지 정확히 알지 못해도 트레이트를 정의할 수 있다.
연관 타입을 사용하는 Deref
가 구현된 String
은 str
로만 변환 가능하다. 다른 타입으로도 변환이 된다면 혼란스러우므로, 자리표시자(Target
등)으로 명시된 하나의 타입만 변환할 수 있다.
제네릭(Generic trait)
제네릭과 비교하면 차이가 드러난다.
제네릭을 사용하는 From
은 어떤 타입에도 구현할 수 있다. 연관 타입과 달리 컴파일러는 변환되는 값의 유형에 따라 어떤 구현을 사용할지 결정할 수 있으므로 모호함(Ambiguity)이 없다.
풀이
// TODO: self의 n제곱을 반환하는 power 메서드를 가진 Power 트레이트를 정의하세요.
// 트레이트 정의와 구현은 테스트를 컴파일하고 통과할 수 있어야 합니다.
// 추천: 모든 케이스를 다루려고 제네릭 구현을 시도하겠지만, 이는 꽤 복잡하고,
// num-traits와 같은 크레이트를 필요로 합니다.
// 그러니 고도의 제네릭 구현을 피하려면 간단한 매크로르 사용하는 편이 좋습니다.
// "Little book of Rust macros"(https://veykril.github.io/tlborm/)를 참고하세요.
// 하지만 꼭 그래야 할 필요는 없습니다. 세 개의 분리된 구현을 작성해도 괜찮습니다.
// 당신이 궁금하다면 더 많은 걸 탐구해보세요.
pub trait Power<RHS = Self> {
type Output;
fn power(self, rhs:RHS) -> Self::Output;
}
impl Power<u16> for u32 {
type Output = u32;
fn power(self, rhs:u16) -> Self::Output {
let mut res = 1;
for _ in 0..rhs {
res *= self;
}
res
}
}
impl Power<u32> for u32 {
type Output = u32;
fn power(self, rhs:u32) -> Self::Output {
let mut res = 1;
for _ in 0..rhs {
res *= self;
}
res
}
}
impl Power<&u32> for u32 {
type Output = u32;
fn power(self, rhs:&u32) -> Self::Output {
let mut res = 1;
for _ in 0..*rhs {
res *= self;
}
res
}
}
#[cfg(test)]
mod tests {
use super::Power;
#[test]
fn test_power_u16() {
let x: u32 = 2_u32.power(3u16);
assert_eq!(x, 8);
}
#[test]
fn test_power_u32() {
let x: u32 = 2_u32.power(3u32);
assert_eq!(x, 8);
}
#[test]
fn test_power_ref_u32() {
let x: u32 = 2_u32.power(&3u32);
assert_eq!(x, 8);
}
}
u32
타입에 대하여 매개변수의 타입만 달라지고 있기 때문에 제네릭 매개변수와 타입 자리표시자를 함께 사용했다.
각각의 테스트에 대해 별도로 동작하는 세 개의 메서드를 구현했다.
리턴 타입은 u32
로 모두 동일하고, rhs
만큼 제곱하여 반환하도록 한다.
제곱은 pow()
가 아니라 직접 구현했다.
&u32
에 대해서는 역참조(*)하여 값을 가져와 그만큼 제곱한다.