Type inference is the ability of the Rust compiler to determine the type of a variable or expression. In many cases, do not need to clearly state the type of a variable when you declare it. The compiler can determine the variable’s type from the type of the expression used to initialize it. For example, if you initialize a variable with the integer literal 12, the compiler can infer that the variable is of an integer type. If no type is specified for a signed integer, the default type inferred by the compiler is i32. Similarly, for an unsigned integer, the default inferred type is u32.
Example:
simple example This example demonstrates type inference for a variable that is initialised with a literal value:
fn main() {
let value = 1.58; // The compiler infers the type of `value` to be f64 by default
// We can confirm the inferred type using print_type_of
print_type_of(&value);
}
// Helper function to print the type name
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
The above code would output f64, confirming the default inferred type.
Output:
f64
Type inference helps improve code readability, similar to typed languages, while still allowing Rust to catch type errors at compile time. Once the compiler has determined the type of a variable, all subsequent assignments to that variable must be of same type. Declaring a variable without initializing it is illegal because the compiler cannot infer its type in that case.
While the compiler can infer types for variables, type specification is required for function arguments; you cannot rely only on type inference for function parameters. However, type inference is still used by the compiler to check that the value passed as an argument is actually of the type declared for that argument. For instance, passing a floating-point number to a function expecting an i16 will generate a compilation error because the types don’t match, even though the floating-point number literal could potentially be inferred as a float.
Type inference is also used in other Situation:
- Closures: Closures do not require obvious type annotations for their parameters or return values, unlike fn functions. This is because closures are typically used in limited contexts, allowing the compiler to infer their types. However, once a closure is called with specific types, those types are inferred and “locked in” for that closure instance. Attempting to call the same closure example with different types later will result in a type error.
- Generics: When working with generic types, you can sometimes use the inferred type syntax _ to let the compiler figure out the concrete type. A common example is using collect() on an iterator, where the type of the collection being created can be inferred from the context using Vec<_>.
Type inference is a powerful feature that allows the compiler to automatically deduce types, reducing verbosity in code while maintaining Rust’s static typing guarantees. It works by analysing contexts and initialisation expressions, but it has limitations, such as requiring explicit type annotations for function parameters.
Rust Topics: