Type annotation on a variable that stores the conversion of user input

Why is a type annotation required for the variable where we want to store the user input conversion?

Is it because the string can be interpreted by different numeric types or because the compiler must know the type of the variable at compile time? Or both at the same time?

Generic functions in Rust are a bit like macros that turn into some concrete functions with concrete types during compilation.

If you have

fn my_function<A, B>(x: A) -> B { …implementation source code… }

then once you (or your dependencies) call my_function::<String, i32>("hello".to_string()) anywhere in the source code, a special version of my_function is generated once, let's call it my_function_string_i32

fn my_function_string_i32(x: String) -> i32 { …same source as `my_function` above… }

and the call to my_function::<String, i32> is replaced by a call to this actual concrete function.

The process of generating such instances of generic functions like my_function_string_i32 is also called “monomorphization”, so my_function is “monomorphised” into (many) functions like my_function_string_i32, and the generic function calls are replaced to calls to the right monomorphized versions in this process (at compile time).

This of course requires that the types for A and B, in this case e.g. String and i32, are actually known during compilation.

For convenience, you often don't have to specify these explicitly.

fn my_function<A, B>(x: A) -> B { unimplemented!() }

// compiles fine
fn test() {
    // all types are explicit
    let s: i32 = my_function::<String, i32>("hello".to_string());
}

// compiles fine
fn test2() {
    // `String` parameter to `my_function` is inferred
    // type of `s` is also inferred
    let s = my_function::<_, i32>("hello".to_string());
}

// compiles fine
fn test3() {
    // `String` and `i32` parameters to `my_function` are inferred
    // type of `s` is explicit and used for inference
    let s: i32 = my_function::<_, _>("hello".to_string());
}

// compiles fine
fn test4() {
    // `String` and `i32` parameters to `my_function` are inferred
    // these were all type arguments, so the whole `::<…>` part can be left out
    // type of `s` is explicit and used for inference
    let s: i32 = my_function("hello".to_string());
}

// error
fn test5() {
    // `String` and `i32` parameters to `my_function` are left to be inferred
    // type of `s` is also left to be inferred
    // ther is not enough information to actually infer the type
    // -->>
    // compilation error!
    let s = my_function("hello".to_string());
}

Rust Playground


This excursion answers your question partially; assuming you're referring by "input conversion" to something like the str::parse function

impl str {
    pub fn parse<F: FromStr>(&self) -> Result<F, F::Err> {
        FromStr::from_str(self)
    }
}

the very mechanism of how generics in Rust work require the type F to be known at compiler time for all use cases.


In fact, this philosophy goes further in Rust; all types need to be known at compile time.[1] This is why you get an error with code like

fn test(s: &str) {
    let parsed = s.parse().unwrap();
}
error[E0284]: type annotations needed
 --> src/lib.rs:2:9
  |
2 |     let parsed = s.parse().unwrap();
  |         ^^^^^^     ----- type must be known at this point
  |
  = note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `parsed` an explicit type
  |
2 |     let parsed: /* Type */ = s.parse().unwrap();
  |               ++++++++++++

whereas once a type is specified like

fn test(s: &str) {
    let parsed: i32 = s.parse().unwrap();
}

it compiles.


Now as for your suggested possible answers

Assuming you're talking about code such as the above in the first place, then the main answer is that the compiler indeed must just know the type of variables at compile time. But the different interpretation also plays a role. Traits like Parse have multiple implementations (literally different functions defined in different places in the standard library or elsewhere) and knowing the type at compiler time helps the compiler inject a call to the correct piece of code into the generated (monomorphized) instance of the parse function.

So my answer would be it’s mainly just the latter “the compiler must know the type of the variable at compile time”, since that’s always a requirement, anyways, but it’s also “both at the same time”, if you want to consider all aspects of this.


  1. Except for lifetime parameters; currently, lifetime parameters – which are arguably part of some types – can be left ambiguous as these don't play a role for the code generation the compiler does, and are the only type information that can be left ambiguous in Rust. Other mechanisms to “remove types” like e.g. dyn Any don't actually make any types unknown; just dyn Any itself is also a known concrete type, and many types can convert to it. ↩︎

2 Likes

I think so too

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.