Type binding is performed at compile time. This is necessary so that the compiler can emit the proper machine instructions (for example, an x86_64
processor doesn't multiply two i32
s the same way it multiplies two i64
s).
Many dynamically-typed languages, such as Python or Lua, will carry type information along with the values (not the variables) and dispatch operations at runtime based on the type of each operand. On the other hand, statically-typed languages, such as C or Rust, usually discard most of the type information; it's not necessary because the machine instructions required to perform the operation were emitted directly in the executable (which makes the statically-typed program faster than the comparable dynamically-typed program).
We can demonstrate that type binding is done at compile time by having the compiler tell us about type errors (this is known as type checking). Here's an example:
fn squared(x: f64) -> f64 {
x * x
}
fn main() {
let a = 2i32;
println!("{}", squared(a));
}
Compiling this gives the following output:
error[E0308]: mismatched types
--> src/main.rs:7:28
|
7 | println!("{}", squared(a));
| ^ expected f64, found i32
The Rust compiler can infer the type of many variables based on usage (similar to how auto
works in C++). When it can't, it'll give an error. For example:
fn main() {
let a;
}
gives this output:
error[E0282]: type annotations needed
--> src/main.rs:2:9
|
2 | let a;
| ^
| |
| cannot infer type for `_`
| consider giving `a` a type
When the compiler encounters errors, it stops and doesn't produce a runnable executable. Since we don't have an executable form of our program, there is no "run time", so the above happens at compile time.