Description
The way in which the types of integer literals are deduced in generic contexts is confusing. For example, the following:
trait Int {}
impl Int for i16 {}
impl Int for i32 {}
trait UInt {}
impl UInt for u16 {}
impl UInt for u32 {}
fn foo<T: Int>(x: T) { println!("size: {}", std::mem::size_of::<T>()); }
fn bar<T: UInt>(x: T) { println!("size: {}", std::mem::size_of::<T>()); }
fn main() {
foo(10);
bar(10);
}
works for foo
but fails for bar
. Weirder is that uncommenting the impl UInt for u16 {}
makes it work.
IMO both cases should either work or fail (I would prefer work).
The error messages is:
error[E0277]: the trait bound `i32: UInt` is not satisfied
--> <anon>:13:3
|
13 | bar(10);
| ^^^ the trait `UInt` is not implemented for `i32`
|
= note: required by `bar`
error: aborting due to previous error
I interpret from this that rustc is only trying with i32
(for whatever reason) even though the code does not contain any explicit mention of i32
anywhere. Still, removing one of the impls of UInt
makes the program type-check, so rustc is able to deduce unsigned integer types in some situations.
EDIT: After going through the book without any luck, I found the following in the Rust language reference:
The type of an unsuffixed integer literal is determined by type inference:
- 1. If an integer type can be uniquely determined from the surrounding program context, the unsuffixed integer literal has that type.
- 2. If the program context under-constrains the type, it defaults to the signed 32-bit integer i32.
- 3. If the program context over-constrains the type, it is considered a static type error.
I guess that removing the impl UInt for u16 {}
switches from rule 2 to rule 1, explaining the behavior.