`{Integer}` type -- Default integer type -- Methods

Spending some time with The Rust Programming Language Book and std documentation and found a behavior I do not understand:

This example runs just fine:

Source:

fn main() {
    let int: i32 = 42;
    let ones = int.count_ones();
    println!("one(s): {}", ones);
}

Output:

$ cargo run
Compiling example v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/example`
one(s): 3

According to "The Book" in section Integer types, the default integer type is i32 but if I remove the type annotation from previous example:

Source:

fn main() {
    let int = 42;
    let ones = int.count_ones();
    println!("one(s): {}", ones);
}

Output:

$ cargo run 
Compiling example v0.1.0 
error[E0599]: no method named `count_ones` found for type `{integer}` in the current scope
 --> src/main.rs:3:20
  |
3 |     let ones = int.count_ones();
  |                    ^^^^^^^^^^ method not found in `{integer}`
  1. What is the type {integer} the rust compiler is referring to ?
  2. Why the rust compiler returns a compiler error even though i32 is the default integer type and count_ones() is defined for i32 ?
    Thanks

relevant part in book:

If you’re unsure, Rust’s defaults are generally good choices, and integer types default to i32 : this type is generally the fastest, even on 64-bit systems.

count_ones() documentation: std::i32::count_ones()

This basically comes down to how Rust does type inference and type defaulting.

{integer} is how rustc prints out the placeholder type for an integer literal. It's not a real type; it needs to collapse to a real iN or uN type to be used.

And this is why trying to call .count_ones() doesn't work. In order to call a method with method call syntax, the type of the variable must be known at that point. In the example without the type annotation, the type isn't known when calling the method (it's still the placeholder type {integer}), so you can't call a method on it.

This error message could definitely be improved, or rustc could learn to deal with this, though it's more complicated than you might think.

Why it's complicated

Currently, it's a built-in limitation of type inference that "backwards propagation" of type information cannot flow over method invocations. Consider the following snippet:

let i = 42;
// i.count_ones();
takes_u32(i);

Here, i is inferred to have the type of u32 because it's given to a function that takes u32. If the method lookup for count_ones did the obvious thing and collapsed the type placeholder {integer} to the default i32, then you would have a type conflict, even though count_ones is defined for u32 as well.

Instead, we'd have to teach rustc what methods are available on all types {integer} can collapse to, so that the method call doesn't need to collapse the type. The current structure of type fallback (which only exists for {integer} IIUC) and the impls on primitives in the standard library do not allow for this in any way, and would have to be reworked to support it.

2 Likes

Thanks for your detailed answer.

Just one follow up question:
Should I annotate all my integer let statement or let the compiler infer the type and raise an error when necessary?

I filed an issue for improving this diagnostic.

In general, you shouldn't add type annotations unless one of two conditions hold:

  • the compiler requires you to, or
  • the type annotation increases the readability (local clarity) of the code.

In most cases, it's better to allow the compiler to infer types, because adding the types is just redundant information to the reader.

And as a side note, for integer literals specifically, in addition to let _: i32 = 42;, you can write 42i32 (or 42_i32) to give the literal an unambiguous type.

1 Like

Thanks for all your advice and for the issue!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.