다음의 예제를 봅시다.
fn main() {
let a = 10;
let b_ref = &20;
assert_eq!(a + b_ref, 30);
assert!(a < b_ref);
}
a
는 i32
타입이고, b_ref
는 &i32
타입입니다. 과연 컴파일러는 뭐라고 할까요?
error[E0277]: can't compare `{integer}` with `&{integer}`
--> src/main.rs:7:15
|
7 | assert!(a < b_ref);
| ^ no implementation for `{integer} < &{integer}` and `{integer} > &{integer}`
|
a + b_ref
는 문제없지만, a < b_ref
는 컴파일러가 씅냅니다. 사칙연산에 대해 value
와 ref
는 허용되지만, 비교연산자(<
<=
>
>=
)는 이를 허용하지 않는 것이죠.
Rust는 Trait로 연산자 오버로딩을 지원합니다. Rust 컴파일러는 연산자들을 특정 메서드 호출로 컴파일합니다.
즉, 사칙연산자들에 대해서는 ref
와 value
간에 Trait가 구현되어 있다는 뜻이죠.
덧셈의 경우 std::ops::Add
Trait의 구현이 필요합니다. Rust에서는 이를 지원하는데, 아래와 같이 value
타입과 ref
타입 모두를 구현해주고 있습니다. 물론 -
*
/
%
모두 구현되어 있습니다.
반면, 비교의 경우 이를 지원하지 않습니다.
물론 평소에는 이게 큰 문제가 되진 않지만, 이 역시 함수형 프로그래밍 방식으로 사용할 땐 문제가 있을 수 있습니다. 예를 들어 어떤 배열에 30 이하의 값만 계산한다고 해봅시다.
fn main() {
let numbers = vec![10, 20, 30, 40, 50];
let result = numbers
.iter()
.filter(|n| n <= 30)
.sum();
assert_eq!(result, 60);
}
문제가 없을 것 같지만, 컴파일되지 않습니다.
error[E0308]: mismatched types
--> src/main.rs:6:26
|
6 | .filter(|n| n <= 30)
| ^^ expected `&&_`, found integer
|
iter()
는 reference를 리턴하고, filter()
역시 클로저의 매개변수를 reference로 넘깁니다. 따라서 filter()
의 n
의 타입은 &&i32
입니다. 이를 해결하려면 &&
를 어떻게든 처리해야 합니다.
fn main() {
let numbers = vec![10, 20, 30, 40, 50];
let result = numbers
.iter()
.filter(|n| **n <= 30) // deref를 두 번 하거나
.sum::<i32>();
assert_eq!(result, 60);
}
fn main() {
let numbers = vec![10, 20, 30, 40, 50];
let result = numbers
.iter()
.filter(|&n| *n <= 30) // &와 패턴매칭하고 deref 하거나
.sum::<i32>();
assert_eq!(result, 60);
}
fn main() {
let numbers = vec![10, 20, 30, 40, 50];
let result = numbers
.iter()
.filter(|&&n| n <= 30) // i32는 Copy trait를 구현하므로 &&와 패턴매칭 하거나
.sum::<i32>();
assert_eq!(result, 60);
}
fn main() {
let numbers = vec![10, 20, 30, 40, 50];
let result = numbers
.into_iter() // into_iter로 &나 *를 덜 쓰게 하거나. 하지만 이 경우, numbers의 ownership이 사라진다!
.filter(|n| *n <= 30)
.sum::<i32>();
assert_eq!(result, 60);
}
방법은 여러가지가 있겠지만, 아무튼 중요한점은 비교의 경우 타입일치가 중요하다는 점을 기억해야 한다는 것입니다.