Reputation: 55
I'm making an app using the rust_decimal crate. Part of my app involves operations where the value '1' is an operand, so I tried using num_traits::identities::one() and ran into some unexpected errors:
use rust_decimal::Decimal;
use num_traits::identities::*;
fn foo(val : Decimal) {
let _1 = one(); // E0282, expected
let _one : Decimal = one(); // Ok, expected
let add : Decimal = one() + val; // E0283, unexpected
let inc : Decimal = val + one(); // E0284, unexpected
}
I am surprised that the compiler cannot figure out what type one() is meant to return in the last two lines. Why is this?
Upvotes: 4
Views: 207
Reputation: 27945
There's only one type¹ that can be assigned to a Decimal
— Decimal
itself — but there may be any number of types that can be added to a Decimal
.
let add: Decimal = one() + val;
one()
must be of some type that implements Add<Decimal, Output = Decimal>
. But there could be many types that satisfy that constraint, and the compiler will not choose one, so you get an error.
let inc: Decimal = val + one();
In this case, if one()
is of type T
, Decimal
must implement Add<T, Output = Decimal>
. But again, there could be many T
s that satisfy this constraint, and the compiler will not choose one.
To fix either error, you can explicitly say that you want the Decimal
version of one
:
let add = one::<Decimal>() + val;
(The : Decimal
annotation on add
is no longer necessary because the Add
implementation unambiguously determines the Output
type.)
Decimal only allows additions with other Decimals (otherwise I'd be using a literal '1' instead), so I'm not sure what the ambiguity is in this particular case.
It doesn't actually matter how many types exist that satisfy the requirements. The compiler does not "go looking" for types to satisfy all the constraints; the types have to be unambiguously determined by only local information. Suppose there was only one type that worked, but it was defined in a third-party crate; should the compiler know about it? Or if there's only one type that works today, but tomorrow you include a new crate and there are two such types — should your code break then? It's to avoid this kind of nonlocal breakage that Rust declines to choose a type for you. In general², the compiler will only deduce types; it doesn't speculate.
This question is a lot like How does one subtract 1 from a BigInt in Rust?
¹Okay, this is not strictly true. Types that can be coerced to Decimal
can also be assigned to Decimal
variables. But coercion is only possible when the compiler knows both sides of the assignment already, so you can't infer through the =
when a coercion is happening.
²There are limited contexts in which the compiler can choose types. See this answer for one example. Autoref-based stable specialization describes using a similar technique. I could not find a way to apply this to your problem.
Upvotes: 1
Reputation: 1858
That is how Rust does operator overloading.
For an expression like a+b
, Rust will first determine the type of a
, lets say it has Type T
and b
has type U
.
When T
implements Add<U, Output = V>
the addition can be compiled and the resulting type will be V
.
I some circumstances the compiler can infer the Type of a
by the context, but this in not the case in your example.
Because one()
has multiple types which implement the Add
Trait it cannot determine which Type one()
should have. It could be that f64
implements Add<Decimal, Output=Decimal>
, which would make your expression ambiguous.
In the expression val + one()
the type of the first operand is determined, but again there a multiple implementations of Add
which could be applied: Add<Decimal, Output=Decimal>
, Add<f64, Output=Decimal
.
All of this can be solved by annotating one()
like this: one::<Decimal>()
. Which has an unambiguous Type.
Upvotes: 2