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. 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.