What is the type `{integer}`?


#1

I stumbled upon this type when i tried to compile the following snippet:

fn main() {
    let a = 0i32;
    let a_pos = a.is_positive();

    let b = 0u32;
    let b_pos = b.is_positive();

    let c = 0;
    let c_pos = c.is_positive();
}

The output of the compiler:

error: no method named `is_positive` found for type `u32` in the current scope
 --> src/main.rs:6:19
  |
6 |     let b_pos = b.is_positive();
  |                   ^^^^^^^^^^^

error: no method named `is_positive` found for type `{integer}` in the current scope
 --> src/main.rs:9:19
  |
9 |     let c_pos = c.is_positive();
  |                   ^^^^^^^^^^^

error: aborting due to 2 previous errors
  • The variable a is of type i32 and as such has the method is_positive().
  • The variable b is of type u32 and this type does not have a method named is_positive(), so I see why the compiler complains.
  • But puzzling for me is the compiler error message for variable c. When I read The Rust Reference, I cannot find a mention of types with curly braces like {integer}. Even more confusing is that I found the following text about integer literals in The Rust Reference:

The type of an unsuffixed integer literal is determined by type inference:

  • If an integer type can be uniquely determined from the surrounding program context, the unsuffixed integer literal has that type.

  • If the program context under-constrains the type, it defaults to the signed 32-bit integer i32.

So I would assume that either the type of c can be uniquely determined from this snippet (yielding type i8, i16, i32 or i64), or it defaults to have type i32 and thus the method call to is_positive() should compile.

So my questions are:

  1. What is the type {integer}?
  2. What’s wrong with my reasoning above that c should have type i32?

Thank you in advance.


#2

u32 and all types like it (u8, u16…) are unsigned integers. That’s why they are prefixed with ‘u’ (unsigned). So “all of them”, all except the 0 that is nor positive nor negative, are positive. You can’t subtract from 0 in all those types.

I think that thing called {integer} is the compiler knowing that is an integer type but not knowing exactly what specific type you are trying to use.


#3

Yes this is correct.


#4

Ok, that answers my first question. Thank you. :slight_smile:

The second question still remains:
The Rust Reference:

The type of an unsuffixed integer literal is determined by type inference:

  • If an integer type can be uniquely determined from the surrounding program context, the unsuffixed integer literal has that type.
  • If the program context under-constrains the type, it defaults to the signed 32-bit integer i32.

So I would assume that either the type of c can be uniquely determined from this snippet (yielding type i8, i16, i32 or i64), or it defaults to have type i32 and thus the method call to is_positive() should compile.


#5

This wording in the reference is poor, IMHO. It’s not “under-constrains and then defaults”, it’s “completely not constrained.” At least, in my understanding.

It’s the difference between “there are no constraints” and “there are not enough constraints to come up with a single answer”.

I’ve filed https://github.com/rust-lang-nursery/reference/issues/29 to improve this wording, or at least double check that my understanding here is correct.


#6

Ok. So should it instead say something along the lines of

If the program context under-constrains the type, it defaults to the abstract integer type {integer}.

?

Now I am a little confused again. My guess is now that {integer} is an abstract type that declares a variable to be an integer with some of its traits like std::ops::Add etc.
But does {integer} reserve a fixed number of bytes, like i32 does? What happens if I use this variable in my program? How many bytes are reserved?

As the type {integer} is actually displayed to the user by rustc, I think it deserves a proper introduction in the reference. What do you think?


#7

It’s not a real type. You can never create a value of {integer}. It’s only there for error messages.


#8

Yes I can. I just do

fn main() {
    let a = 0;
}

And voilà… there is a value of type {integer}. Is it not? Of which type is it?


#9

There is no {integer} type.

There are multiple integer types: u32, i32, u8, usize, etc. When using integer literals, you can specify the type explicitly, like so:

let a = 42u64;
let b = 10i16;

If you don’t, the compiler has to guess the type you have meant (type inference). It can, for example, deduce the correct type if you specify the type of the variable you assign that literal to:

let a: u64 = 42;
let b: i16 = 10;

If the compiler cannot deduce the correct integer type (actually, as @steveklabnik pointed out, only if there are no constraints whatsoever), it (by default) sets it to be i32. This is exactly what is going to happen in your example; the variable a will have type i32.

Now, what is that {integer} thing? When the compiler is not really sure about what (integer) type a value has and it needs to output the type for some reason (such as the type appearing in an error message), it outputs the string "{integer}", meaning “some integer type, but I’m not sure which one, exactly”. Again, this is not a type by itself, this is a placeholder for a real type when the compiler is not sure what the real one is.


#10

The compiler says, there is.

No. I don’t think that’s correct. If a has type i32, then please add one line to the program:

fn main() {
    let a = 0;
    let a_pos = a.is_positive();
}

See?

I get the concept of “some integer type, but I’m not sure which one, exactly”. But that’s a rather human understanding of what’s going on.
What I want to understand is, how the compiler handles this type when translating e.g. into assembler. Somehow it must determine the actual type (i32 or something). But then again, the above program should compile.

I think there is some lack in the documentation here, because if the compiler outputs “type {integer}”, then this type should appear in the docs. Right?


#11

No, there is no concrete {integer} type. You cannot compile a program that relies on {integer}. It exists only for error messages, and will prevent your program from compiling since it is not a concrete type. It is purely abstract, only existing in the mind of the compiler. You can write programs where the compiler will talk about {integer}, but you can never use an {integer}.


#12

This example is quite weird, because a should have type i32 here (just as withouth the is_positive call). But this doesn’t prove that {integer} type somehow exists! This example just shows that the Rust compiler sometimes is not smart enough. Probably calling a method on a value somehow breaks type inference – it requires the type to be already known. In the ideal world, adding a is_positive call should narrow the possibilities from every integer type to just signed ones – {i8, i16, i32, i64, isize}, and then the “default is i32 rule” should kick in and leave you with exactly i32, but unfortunatelly currently this "default is i32" rule seams only to kick in where there are literally no other “hints” about the type.

There’s also a similar bug for non-integers, check out this example:

fn foo() -> Vec<u8> {
    // type of `v` is inferred from the `foo` return type
    let mut v = (0..10).collect();
    // v.push(42); // uncommenting this line confuses Rust
    v
}

#13

The fact that the compiler mentions {integer} does not imply that {integer} exists as an actual type, abstract or otherwise. But I agree with @jaystrict that the fact that the compiler can mention {integer} should imply that some documentation should point out what that means.

The problem with the let a = 0; example program is that adding a the a.is_positive() to it creates another, entirely different, program. The rust compiler does not decide on which type a specific variable has line by line, but method by method.


#14

Then again, what does prove that any type somehow exists? How would you prove that i32 exists?

You may say that let a : i32; compiles but let a : {integer}; does not compile.
But then I answer with "Aha, so you regard the compiler as the ultimate authority of what types do exist? Then have a look at the error message written by the compiler

error: no method named `is_positive` found for type `{integer}` in the current scope

. The compiler talks about a type called {integer}."

Interesting. Maybe this is actually the same “bug”.
Maybe this has to do with the number of parsing runs the compiler runs on a method.


#15

Thank you. :slight_smile:

Are you sure? Because IMHO, the following two functions then should both compile, but only one() compiles.

fn one() {
    let a = 0;
    take_some(a);
    let pos = a.is_positive();
}

fn two() {
    let a = 0;
    let pos = a.is_positive();
    take_some(a);
}

fn take_some(var: i32) {
    // do nothing
}

#16

Maybe we can ask a different question instead of is there a type {integer}, like how can we make that kind of compiler errors easier to understand? :slight_smile:

  • what if the compiler would talk about some unconstrained integer type instead of type `{integer}`?
  • or what if there was an additional note: the concrete type of `a` can not be inferred exactly, there are multiple integer types in question.?

#17

The compiler does not go strictly line-by-line (or statement-by-statement), but it seems it does not analyze the full method as well as I thought before deciding on a type either.

This may be an area where future improvement (without breaking stuff) is possible.


#18

No. For a language like C/C++/JavaScript, the ultimate authority is “the standard”, such as C++11, no matter what compilers actually choose to implement. For Rust, this would be the official documentation: the language reference, the book, the RFCs; the compiler still is merely an implementation.

There are even bugs in the compiler (unfortunately) that prevent it from accepting valid Rust programs or make it accept invalid ones.

There is no type {integer}. Why the compiler sometimes outputs such a string in place of a type is an entirely different question. But even that is not because the compiler believes {integer} type is a thing, no, it’s because it’s often impossible to unambiguously infer the correct type in an incorrect Rust program. In a correct Rust program, all the variables (and values) have definite types.

Again, {integer} isn’t a thing in correct Rust. It’s only a way to talk about incorrect Rust.

I did not have any problem understanding all of this without any kind of documentation, but I agree that mentioning this in some sort of docs is probably a good idea.


#19

And then again, what the compiler accepts as input and what it outputs in error messages are distinct things. The former has to do with whether the program is correct or not (ideally, it’d be the same, but bugs happen); the latter has more to do with the internal representation of AST, type system, etc inside the compiler.


#20

I believe the discussion whether {integer} is a type is not very helpful right now. To me it feels like splitting a hair without getting anywhere about the fundamental problem.

But I believe that there is some consensus that the combination of the compiler mentioning some type `{integer}` and the documentation not mentioning something like that is, well, not optimal. Right?

So, why don’t we start from there? :wink: