Reputation: 884
Why does this work fine:
let items = [1, 2, 3];
let mut cumulator = 0;
for next in items.iter() {
cumulator += next;
}
println!("Final {}", cumulator);
But this fail?:
let items = [1, 2, 3];
let mut cumulator = 0;
for next in items.iter() {
cumulator += next.pow(2);
}
println!("Final {}", cumulator);
Error on .pow(2)
:
no method named `pow` found for reference `&{integer}` in the current scope
method not found in `&{integer}`rustc (E0599)
My IDE identifies next
as i32 and the first code example works fine. But the compiler has an issue the moment I reference next.pow()
or any function on next
. The compiler complains that next
is an ambiguous integer type.
Sure, I can fix this by either explicitly declaring the array as i32[]
. Or I can also use an interim variable before cumulator
which is also explicitly declared i32
. But these seem unnecessary and a bit clunky.
So why is compiler happy in the first case and not in the second?
Upvotes: 11
Views: 912
Reputation: 70397
Calling methods on objects is kind of funny, because it conveys zero information. That is, if I write
a + b
Then, even if Rust knows nothing about a
and b
, it can now assume that a
is Add
where the Rhs
type is the type of b
. We can refine the types and, hopefully, get more information down the road. Similarly, if I write foobar()
, where foobar
is a local variable, then Rust knows it has to be at least FnOnce
.
However, if foo
is a variable, and I write
foo.frobnicate()
Then Rust has no idea what to do with that. Is it an inherent impl
on some type? Is it a trait function? It could be literally anything. If it's inherent, then it could even be in a module that we haven't imported, so we can't simply check everything that's in scope.
In your case, pow
isn't even a trait function, it's actually several different functions. Even if it was a trait function, we couldn't say anything, because we don't, a priori, know which trait. So Rust sees next.pow(2)
and bails out immediately, rather than trying to do something unexpected.
In your other case, Rust is able to infer the type. At the end of the function, all it knows about the type is that it's an {integer}
on which Add
is defined, and Rust has integer defaulting rules that kick in to turn that into i32
, in the absence of any other information.
Could they have applied integer defaulting to next.pow(2)
? Possibly, but I'm glad they didn't. Integers are already a special case in Rust (integers and floats are the only types with polymorphic literals), so minimizing the amount of special casing required by the compiler is, at least in my mind, a good thing. The defaulting rules kicked in in the first example because nothing caused it to bail out, and they would have in the second if it hadn't already encountered the bigger error condition of "calling an impl
function on an unknown type".
Upvotes: 11