Rust Error E0369 Binary Operation Cannot Be Applied
E0369 means an operator like + or == was used on a type that does not implement the matching trait. Fix it with a derive, a trait bound on the generic, or an explicit impl.

E0369 appears when you apply a binary operator to a type that has no implementation of the trait behind it. a + b is sugar for Add::add(a, b), a == b for PartialEq::eq(&a, &b), so a type without that trait cannot use the symbol. There are four places you meet this: your own struct, a generic function, a reference, and a foreign type. All snippets compiled on Rust 1.99, edition 2024.
Your own struct has no operators yet
struct Money {
cents: i64,
}
fn main() {
let a = Money { cents: 250 };
let b = Money { cents: 175 };
let total = a + b;
println!("{}", total.cents);
}error[E0369]: cannot add `Money` to `Money`
--> src/main.rs:8:19
|
8 | let total = a + b;
| - ^ - Money
| |
| Money
|
note: an implementation of `Add` might be missing for `Money`
--> src/main.rs:1:1
|
1 | struct Money {
| ^^^^^^^^^^^^ must implement `Add`
note: the trait `Add` must be implemented
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/ops/arith.rs:76:0Rust never invents an operator for a struct, even when every field would support it, because + on money might mean currency checks, saturation or rounding, and that is the author's decision. Implement std::ops::Add yourself:
use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]
struct Money {
cents: i64,
}
impl Add for Money {
type Output = Money;
fn add(self, rhs: Money) -> Money {
Money { cents: self.cents + rhs.cents }
}
}
fn main() {
let a = Money { cents: 250 };
let b = Money { cents: 175 };
println!("{:?}", a + b);
println!("{}", a == Money { cents: 250 });
}Money { cents: 425 }
truetype Output is part of the contract: adding two Money values yields whatever you declare. It is why Instant - Instant can return a Duration, and it is the line most people forget to write at first. The == works here because of the PartialEq derive, which is the only operator trait that derives; arithmetic always needs a handwritten impl.
Comparison operators need PartialEq or PartialOrd
struct Version {
major: u32,
minor: u32,
}
fn main() {
let a = Version { major: 1, minor: 4 };
let b = Version { major: 1, minor: 9 };
println!("{}", a < b);
}error[E0369]: binary operation `<` cannot be applied to type `Version`
--> src/main.rs:9:22
|
9 | println!("{}", a < b);
| - ^ - Version
| |
| Version
|
note: an implementation of `PartialOrd` might be missing for `Version`
--> src/main.rs:1:1
|
1 | struct Version {
| ^^^^^^^^^^^^^^ must implement `PartialOrd`
help: consider annotating `Version` with `#[derive(PartialEq, PartialOrd)]`
|
1 + #[derive(PartialEq, PartialOrd)]
2 | struct Version {
|A derive fixes it, and derives compare fields in declaration order, which is exactly what you want for a version:
#[derive(PartialEq, PartialOrd)]
struct Version {
major: u32,
minor: u32,
}
fn main() {
let a = Version { major: 1, minor: 4 };
let b = Version { major: 1, minor: 9 };
println!("{}", a < b);
}trueField order matters. Swap the two fields and 1.4 < 1.9 becomes a comparison of minor first. The relationship among the four comparison traits is in PartialEq, Eq, PartialOrd and Ord.
A generic function knows nothing about T
This is the most frequent real-world source. Inside fn sum<T> the compiler has no idea what T can do, so even + is rejected:
fn add_all<T>(a: T, b: T, c: T) -> T {
a + b + c
}
fn main() {
println!("{}", add_all(1, 2, 3));
}error[E0369]: cannot add `T` to `T`
--> src/main.rs:2:7
|
2 | a + b + c
| - ^ - T
| |
| T
|
help: consider restricting type parameter `T` with trait `Add`
|
1 | fn add_all<T: std::ops::Add>(a: T, b: T, c: T) -> T {
| +++++++++++++++The error is deliberately exact: it names the missing bound. Apply it, and note the Output = T, which says that adding two T yields another T. Without it, a + b has an unknown type, and adding a third value would fail again:
use std::ops::Add;
fn add_all<T: Add<Output = T>>(a: T, b: T, c: T) -> T {
a + b + c
}
fn main() {
println!("{}", add_all(1, 2, 3));
println!("{}", add_all(0.5, 0.25, 0.125));
println!("{}", add_all(String::from("a"), "b".into(), "c".into()));
}error[E0277]: cannot add `String` to `String`
--> src/main.rs:10:20
|
10 | println!("{}", add_all(String::from("a"), "b".into(), "c".into()));
| ^^^^^^^ ----------------- `String` doesn't satisfy the trait bound
| |
| no implementation for `String + String`
|
help: the trait `Add<String>` is not implemented for `String`
but trait `Add<&str>` is implemented for it
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/alloc/src/string.rs:2808:0
= help: for that trait implementation, expected `&str`, found `String`
note: required by a bound in `add_all`
--> src/main.rs:3:15
|
3 | fn add_all<T: Add<Output = T>>(a: T, b: T, c: T) -> T {
| ^^^^^^^^^^^^^^^ required by this bound in `add_all`The last call fails to compile, and it is worth seeing why: String + String does not exist, because the standard library implements Add<&str> for String instead. The bound Add<Output = T> means "T plus T", which a String cannot satisfy. That is a real limitation, not a compiler quirk, and the next section shows the shape that handles it.
Operators on references need their own bound
For concrete integers, &a + &b works, because the standard library implements Add for references to numbers. In generic code, T: Add covers values only. If you want to add without taking ownership, ask for the reference implementation with a higher-ranked bound:
use std::ops::Add;
fn total_of<T>(items: &[T], zero: T) -> T
where
T: Clone,
for<'a> &'a T: Add<&'a T, Output = T>,
{
let mut acc = zero;
for x in items {
acc = &acc + x;
}
acc
}
fn main() {
println!("{}", total_of(&[1, 2, 3, 4], 0));
println!("{}", total_of(&[1.5, 2.5], 0.0));
}10
4That signature is heavy. If your function only needs numbers, T: Copy + Add<Output = T> is far easier to read, and the call site passes copies. Reach for the for<'a> form only when T is expensive to clone, such as a big-integer type.
Foreign types: the orphan rule gets in the way
You cannot implement Add (a foreign trait) for Vec<i32> (a foreign type), because of the orphan rule. The fix is a newtype that you own:
use std::ops::Add;
#[derive(Debug)]
struct Vector(Vec<i32>);
impl Add for Vector {
type Output = Vector;
fn add(self, rhs: Vector) -> Vector {
Vector(self.0.iter().zip(rhs.0.iter()).map(|(a, b)| a + b).collect())
}
}
fn main() {
let v = Vector(vec![1, 2, 3]) + Vector(vec![10, 20, 30]);
println!("{v:?}");
}Vector([11, 22, 33])The orphan rule is covered in full in the orphan rule, and the pattern in the newtype pattern. If you are adding two vectors in anger, a crate like nalgebra already provides the operators and the right semantics for mismatched lengths, where this example silently truncates.
Mixed types and the Rhs parameter
Add is really Add<Rhs = Self>. To allow money + cents you implement the trait a second time with a different right-hand type, and the compiler picks by operand type:
use std::ops::Add;
#[derive(Debug, Clone, Copy)]
struct Money(i64);
impl Add for Money {
type Output = Money;
fn add(self, rhs: Money) -> Money {
Money(self.0 + rhs.0)
}
}
impl Add<i64> for Money {
type Output = Money;
fn add(self, rhs: i64) -> Money {
Money(self.0 + rhs)
}
}
fn main() {
println!("{:?}", Money(100) + Money(50));
println!("{:?}", Money(100) + 25);
}Money(150)
Money(125)It is not symmetric: 25 + Money(100) still fails, because that would be impl Add<Money> for i64, and the orphan rule forbids it since i64 is foreign and the trait is foreign. Document the one direction you support.
What the operator really calls
An operator is a method call written in a shorter form, and writing the long form once makes every E0369 easier to read. The expression a + b becomes Add::add(a, b), and the type of a decides which implementation runs. The trait takes its arguments by value, which is why the compiler moves non-Copy operands into the call.
use std::ops::Add;
#[derive(Debug)]
struct Tag(String);
impl Add for Tag {
type Output = Tag;
fn add(self, rhs: Tag) -> Tag {
Tag(format!("{}-{}", self.0, rhs.0))
}
}
fn main() {
let a = Tag("net".into());
let b = Tag("io".into());
let joined = a + b;
println!("{joined:?}");
println!("{a:?}");
}error[E0382]: borrow of moved value: `a`
--> src/main.rs:18:16
|
14 | let a = Tag("net".into());
| - move occurs because `a` has type `Tag`, which does not implement the `Copy` trait
15 | let b = Tag("io".into());
16 | let joined = a + b;
| ----- `a` moved due to usage in operator
17 | println!("{joined:?}");
18 | println!("{a:?}");
| ^ value borrowed here after move
|
note: if `Tag` implemented `Clone`, you could clone the value
--> src/main.rs:4:1
|
4 | struct Tag(String);
| ^^^^^^^^^^ consider implementing `Clone` for this type
...
16 | let joined = a + b;
| - you could clone this value
note: calling this operator moves the left-hand side
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/ops/arith.rs:91:11This is the same ownership rule from E0382 arriving through an operator. Adding two Tag values consumed both, because add takes self by value. Types that are cheap to copy derive Clone and Copy so this never bites, and types that are not provide impl Add for &Tag as well.
The sibling errors: += and unary minus
Compound assignment and unary operators have their own traits, and each has its own code, so E0369 is only the binary family. += uses AddAssign (E0368), and -x uses Neg (E0600). Implementing Add does not give you +=, because the assigning form can often avoid allocating a new value.
use std::ops::{AddAssign, Neg};
#[derive(Debug, Clone, Copy)]
struct Money(i64);
impl AddAssign for Money {
fn add_assign(&mut self, rhs: Money) {
self.0 += rhs.0;
}
}
impl Neg for Money {
type Output = Money;
fn neg(self) -> Money {
Money(-self.0)
}
}
fn main() {
let mut balance = Money(500);
balance += Money(250);
println!("{balance:?}");
println!("{:?}", -balance);
}Money(750)
Money(-750)A rule of thumb that saves time: when you add an operator to a type, ask whether callers will also want the assigning form and the reference form, and implement them in the same change. A type where a + b works but a += b does not feels unfinished to the person using it.
Reading the error when it is long
E0369 messages can run to fifteen lines because the compiler tries to help. Three lines carry the information. The first names the operator and the type on the left. The one with the caret under the operator shows which side was inspected, since Rust picks the implementation from the type of the left operand. The last help or note shows the bound or derive that would fix it. If you cannot see a derive suggestion, the trait is not derivable and you need an impl, which is the case for every arithmetic operator.
One more practical point: a call to a function with a missing operator bound reports the error inside the function, not at the call. A generic helper that compiles for the first caller and breaks at the second means the bound is missing from the signature, and that error will only surface when somebody passes a type that happens to lack the operator. Writing the bound on T up front turns that late surprise into a signature the caller can read.
When an operator is the wrong design
Operators are for types where the operation is obvious and total. For fallible arithmetic, such as money that must not overflow or must have matching currencies, an Add that panics or silently wraps is a hazard. Provide checked_add returning Option or Result, the way the integer types do, and keep + for the unchecked case you are willing to document. The error family this belongs to is in E0277.


