Are operators in core really defined circularly?

  • A+
Category:Languages

We can implement the traits in core::ops to define the behavior of operators for our types. The traits themselves are annotated with #[lang =...] attributes so the compiler knows which traits and operators belong together.

For example, the Add implementation for primitive types looks like this (macro manually expanded and simplified from here):

impl Add for i32 {     type Output = i32;      fn add(self, other: i32) -> i32 {         self + other     } } 

To my surprise, the implementation uses the + operator internally, which presumably calls self.add(other), resulting in an infinite recursion. Obviously, things do not happen like this because expressions like 3 + 4 (assuming no constant folding) work perfectly fine.

Now consider this naive implementation of the Add trait:

use std::ops::Add;  struct Foo;  impl Add for Foo {     type Output = Foo;      fn add(self, other: Foo) -> Foo {         self + other     } }  fn main() {     let two_foo = Foo + Foo; } 

The compiler warns that function cannot return without recurring and running this program in Debug mode properly stops with fatal runtime error: stack overflow.

How does the compiler know how to add two numbers without falling into a recursive loophole?


How does the compiler know to add two numbers without falling into a recursive loophole?

Because the compiler is the compiler, and the compiler knows it doesn't need an Add implementation to add two numbers. If it's doing constant folding, it just adds them. If it's generating code, it tells LLVM to add them at runtime.

Those Add implementations aren't there to tell the compiler how to add numbers, they're to implement Add for numbers so that user code can add numbers via the Add trait just like any user-defined type. If those implementations didn't exist, then you wouldn't be able to add numbers in generic methods, because they wouldn't implement Add.

To put it another way: Add is what the compiler uses when it doesn't otherwise know how to add things. But it already knows how to add numbers, so it doesn't need them. They're provided for consistency with other types.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: