Rust 에러 E0369: binary operation cannot be applied to type (연산자 미지원)
E0369는 ==, <, + 같은 연산자를, 아직 그 연산을 할 줄 모르는 타입에 썼다는 뜻이에요. Rust에서 연산자는 내가 만든 구조체나 열거형에 원래부터 들어 있지 않아요. 연산자마다 트레이트가 하나씩 있고, 그 트레이트를 derive하거나 구현해야 타입이 연산자를 쓸 수 있어요. 보통은 #[derive(...)] 한 줄이면 고쳐지고, 어떤 걸 붙여야 하는지도 컴파일러가 알려 줘요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.
에러 메시지
섹션 제목: “에러 메시지”error[E0369]: binary operation `==` cannot be applied to type `Point` --> src/main.rs:9:10 |9 | if a == b { // ❌ Point는 자기 자신을 비교하는 법을 몰라요 | - ^^ - Point | | | Point |note: an implementation of `PartialEq` might be missing for `Point` --> src/main.rs:1:1 |1 | struct Point { | ^^^^^^^^^^^^ must implement `PartialEq`help: consider annotating `Point` with `#[derive(PartialEq)]` |1 + #[derive(PartialEq)]2 | struct Point { |“이항 연산 ==를 Point 타입에 적용할 수 없다”는 뜻이에요. note는 “Point에 PartialEq 구현이 빠진 것 같다”고 말하고, help는 “Point에 #[derive(PartialEq)]를 붙여 보라”고 제안해요.
이 에러를 내는 프로그램은 다음과 같아요.
struct Point { x: i32, y: i32,}
fn main() { let a = Point { x: 1, y: 2 }; let b = Point { x: 1, y: 2 }; if a == b { // ❌ Point는 자기 자신을 비교하는 법을 몰라요 println!("Same point!"); }}첫 줄은 연산자에 따라 달라지지만, 같은 에러예요.
binary operation `<` cannot be applied to type `Version`:PartialOrd가 없어요(>,<=,>=도 마찬가지예요).cannot add `Point` to `Point`:Add가 없어요. “cannot subtract”(뺄 수 없음), “cannot multiply”(곱할 수 없음) 같은 메시지도 볼 수 있어요.cannot add `&str` to `&str`: 문자열 슬라이스 두 개를+로 이어 붙이려고 했어요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”연산자 아래의 라벨은 양쪽의 타입을 보여 줘요. 여기서는 Point와 Point예요. 그다음 note:가 내 구조체를 가리키면서 PartialEq를 구현해야 한다(must implement PartialEq)고 말해요. 다시 말해 “Point 두 개를 비교하라고 했는데, Point에서 같다는 게 무슨 뜻인지 아무도 알려 주지 않았어요”라는 거예요.
경기 심판에게 물건 두 개를 건네면서 “어느 쪽이 더 커요?“라고 묻는다고 생각해 보세요. 숫자라면 답이 뻔해요. 하지만 배낭 두 개라면 심판에게 먼저 기준이 필요해요. 무게로 잴까요? 부피로 잴까요? Rust는 바로 이렇게 꼼꼼한 심판이에요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”모든 연산자는 사실 변장한 메서드 호출이에요. a == b는 PartialEq::eq를, a < b는 PartialOrd::partial_cmp를, a + b는 Add::add를 호출해요. 숫자, String, 그리고 많은 표준 타입은 이미 이 트레이트들을 구현하고 있어요. 하지만 새로 만든 구조체는 하나도 없는 상태로 시작해요.
Rust는 추측하지 않아요. 추측은 가끔 틀리기 때문이에요. 두 User는 ID만 같으면 같은 걸까요, 아니면 모든 필드가 같아야 할까요? Point + Point는 좌표를 더해야 할까요, 아니면 점을 더한다는 것 자체가 말이 안 될까요? 트레이트를 derive하거나(필드별로 비교하는 기본 방식) 직접 impl을 써서 내가 정하면 돼요. 이 트레이트들은 자주 쓰는 트레이트 페이지에서 설명해요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: ==와 !=에는 #[derive(PartialEq)]
섹션 제목: “방법 1: ==와 !=에는 #[derive(PartialEq)]”“같다”가 “모든 필드가 같다”는 뜻일 때 ==와 !=를 위해 이 방법을 고르세요. 대부분은 이게 정답이고, rustc가 제안하는 것도 바로 이거예요. Debug도 같이 붙이면 구조체를 출력할 수 있어요.
#[derive(Debug, PartialEq)]struct Point { x: i32, y: i32,}
fn main() { let a = Point { x: 1, y: 2 }; let b = Point { x: 1, y: 2 }; let c = Point { x: 5, y: 0 }; if a == b { println!("같은 점이에요!"); } println!("{:?} != {:?}: {}", a, c, a != c);}실행 결과:
같은 점이에요!Point { x: 1, y: 2 } != Point { x: 5, y: 0 }: true모든 필드도 비교할 수 있어야 해요. 어떤 필드의 타입에 PartialEq가 없다면, 그 타입에도 derive해 주세요. 열거형도 똑같아요. enum Status에 #[derive(PartialEq)]를 붙이면 status == Status::Banned가 컴파일돼요.
방법 2: <와 >에는 #[derive(PartialOrd)]
섹션 제목: “방법 2: <와 >에는 #[derive(PartialOrd)]”타입에 자연스러운 순서가 있을 때 이 방법을 고르세요. PartialOrd에는 PartialEq가 필요해서, rustc는 #[derive(PartialEq, PartialOrd)]를 제안해요. derive한 버전은 필드를 위에서 아래로 비교해요. 먼저 major를 비교하고, 그게 같을 때만 minor를 비교해요. 그래서 구조체 안의 필드 순서가 중요해요.
#[derive(Debug, PartialEq, PartialOrd)]struct Version { major: u32, minor: u32,}
fn main() { let installed = Version { major: 1, minor: 4 }; let required = Version { major: 1, minor: 7 }; if installed < required { println!("{:?}에서 업데이트해 주세요", installed); }
let next = Version { major: 2, minor: 0 }; println!("2.0이 1.7보다 새 버전인가요: {}", next > required);}실행 결과:
Version { major: 1, minor: 4 }에서 업데이트해 주세요2.0이 1.7보다 새 버전인가요: true내 타입의 Vec을 .sort()하고 싶다면 Eq와 Ord도 필요해요. 정렬을 참고하세요.
방법 3: +에는 impl std::ops::Add
섹션 제목: “방법 3: +에는 impl std::ops::Add”좌표나 돈을 더하는 것처럼, 내 타입의 두 값을 더하는 게 분명한 의미가 있을 때 이 방법을 고르세요. 수학 연산자에는 derive가 없으니까 짧은 impl을 직접 써요. Output은 +가 돌려주는 타입이에요.
use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]struct Point { x: i32, y: i32,}
impl Add for Point { type Output = Point;
fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } }}
fn main() { let start = Point { x: 1, y: 2 }; let step = Point { x: 3, y: 4 }; let end = start + step; println!("{:?}", end); println!("{}", end == Point { x: 4, y: 6 });}실행 결과:
Point { x: 4, y: 6 }trueSub, Mul, Div, Neg도 모두 std::ops에 있고, 똑같은 방식으로 구현해요. 여기서 Copy를 derive했기 때문에 + 뒤에도 start와 step을 계속 쓸 수 있어요. 더 자세한 내용은 자주 쓰는 트레이트와 고급 트레이트에 있어요.
방법 4: +나 format!으로 문자열 이어 붙이기
섹션 제목: “방법 4: +나 format!으로 문자열 이어 붙이기”에러가 cannot add `&str` to `&str`라고 할 때 이 방법을 고르세요. 문자열에 +를 쓰려면 왼쪽에는 소유한 String이, 오른쪽에는 &str이 있어야 해요. rustc의 help도 “create an owned String from a string reference”(문자열 참조로부터 소유한 String을 만들라)라고 자기 말로 알려 줘요. 그러니까 first.to_owned() + second예요. String이 두 개라면 오른쪽을 빌리세요. s1 + &s2처럼요. 그리고 format!은 언제나 동작하고, 아무것도 이동시키지 않아요.
fn main() { let first = String::from("안녕하세요, "); let second = String::from("Ferris");
let shout = format!("{first}{second}!"); // 둘 다 빌려요 let joined = first + &second; // first는 joined 안으로 이동해요 let short = "안녕, ".to_owned() + "꽃게야"; // 왼쪽이 &str이면 먼저 소유한 값으로 만들어요
println!("{shout}"); println!("{joined}"); println!("{short}"); println!("{second}는 아직 여기 있어요");}실행 결과:
안녕하세요, Ferris!안녕하세요, Ferris안녕, 꽃게야Ferris는 아직 여기 있어요+는 왼쪽 String의 소유권을 가져가서 그 뒤에 이어 붙여요. 그래서 그 줄 다음에는 first를 쓸 수 없어요. 헷갈린다면 그냥 format!을 쓰세요. 문자열을 참고하세요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”제네릭 코드에서 비교할 때. 유명한 largest 함수는 binary operation `>` cannot be applied to type `&T`로 실패해요. T는 어떤 타입이든 될 수 있고, 순서가 없는 타입일 수도 있기 때문이에요.
fn largest<T>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { // ❌ T는 비교할 수 없는 타입일 수도 있어요 largest = item; } } largest}해결: rustc가 제안하는 그대로 트레이트 바운드를 추가하세요. fn largest<T: PartialOrd>(list: &[T]) -> &T. 제네릭을 참고하세요.
열거형 값을 확인할 때. if status == Status::Banned는 열거형에 #[derive(PartialEq)]가 붙기 전까지 실패해요. 열거형을 바꿀 수 없다면(다른 크레이트에서 온 경우), 대신 matches!(status, Status::Banned)나 match를 쓰세요.
String 두 개를 이어 붙일 때. String 두 개로 first + second를 쓰면 E0369가 아니에요. help: consider borrowing here(여기서 빌려 보라)와 함께 E0308(“expected &str, found String”)이 나요. 해결은 first + &second예요. 같은 종류의 문제고, 방법 4와 해결책도 같아요.
서로 다른 두 타입을 비교할 때. i32인 count와 f64인 limit으로 count == limit을 쓰면 E0308이 나고, String과 char로 name == 'F'를 쓰면 E0277(“can’t compare String with char”)이 나요. 기억하기 쉬운 규칙: 왼쪽 타입에 그 연산자가 아예 없으면 E0369, 연산자는 있지만 그 오른쪽 타입과는 안 될 때는 E0308이나 E0277이에요. 해결: 한쪽을 변환하세요(count as f64 == limit, name == "F").
관련 에러
섹션 제목: “관련 에러”- E0277: trait bound not satisfied (트레이트 바운드 불충족): 왼쪽 타입에 연산자는 있지만, 이 오른쪽 타입과는 안 돼요(
i32 + f64,String == char). - E0308: mismatched types (타입 불일치):
String + String이나 서로 다른 두 구조체를 비교할 때 나와요. - E0599: no method found (메서드 없음): 내 타입에
Ord나PartialEq가 없으면.sort()나.contains()호출도 같은 이유로 실패할 수 있어요. E0368: binary assignment operation += cannot be applied(복합 대입 연산 불가): 이 에러의+=버전이에요.std::ops의AddAssign을 구현하세요.- 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 구조체 Order에 if order_a == order_b를 썼더니 E0369가 났어요. 보통 어떻게 고칠까요?
2. struct Version { major: u32, minor: u32 }에 #[derive(PartialOrd)]를 붙이면 두 버전을 어떻게 비교할까요?
3. first와 second가 둘 다 &str이에요. 에러 없이 이어 붙이는 식은 무엇일까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0369 — Rust error codes index (영어): 이 에러의 공식 설명이에요.
- C - 파생 가능한 트레이트 — The Rust Programming Language 한국어판:
PartialEq와PartialOrd를 derive하면 무엇을 얻는지, 필드별 비교 순서까지 설명해요. - 문자열에 UTF-8 텍스트 저장하기 — The Rust Programming Language 한국어판: 문자열의
+가 왜 왼쪽에String, 오른쪽에&str을 받는지, 언제format!을 쓰면 좋은지 알려 줘요. - Operator Overloading — Rust by Example (영어): 내 타입에
Add를 구현하는 짧은 실행 예제예요. std::ops::Add— 표준 라이브러리 문서 (영어):Add트레이트와Output타입, 서로 다른 타입의 값을 더하는 방법을 알 수 있어요.