RustType System

Why type declarations for Rust constants should be unnecessary

How inconsistent rules unnecessarily complicate Rust's type system

An introduction to variables in Rust

The let keyword

A let statement is used to introduce one or more variables into the current scope, as given by a pattern. These statements can also be used to shadow existing variables and to declare a specified variable as mutable.

let name = "Jhon";
println!("{name}"); // Jhon
let name: &str = "John";
println!("{name}"); // John
let mut two = 1;
println!("{two}"); // 1
two += 1;
println!("{two}"); // 2
The const keyword

The const keyword is used to define variables that contain values which are expected to remain unchanged for the duration of the program. Constants live for the entire lifetime of a program and have no fixed address in memory. This is because they're effectively inlined to each place that they're used. Because of this, references to the same constant are not necessarily guaranteed to refer to the same memory address.

const NAME: &str = "John Smith";

As implied by the name, the compiler panics if your try to change a constant value.

The static keyword

Similar to a const variable, except that it represents a precise memory location in the program. All references to a static refer to the same memory location. Static items have a 'static lifetime, which outlives all other lifetimes in a Rust program. Static items do not call drop at the end of the program.

static TWO: i32 = 2;

While you can declare a static mut, it is considered to be bad practice.

Rust's "inability" to infer types

As demonstrated before, mutable variables created using the let keyword can be defined with or without type annotations:

let var_name = 10;
let var_name: i32 = 10;

However, when dealing with constant values you must always specify a type:

const VAR_NAME: i32 = 10;

The Rust docs state that:

Constants must always be statically typed.

If this requirement is not satisfied, the compiler panics and throws the following error:

error: expected `:`, found `=`
--> src/
3 |   const VAR_NAME = 10;
                ^ expected `:`

This seemingly arbitrary rule is confusing, especially when you consider the fact that the Rust compiler is more than capable of inferring the type of a constant / static variable.

Somehow, Rust cannot infer types for constants, however this doesn't seem to be an issue for variables declared with let, even for Complex types.

Rust has two different types of constants which can be declared in any scope including global. Both require explicit type annotation.

If the compiler might infer that a const variable with a value of 22 is of type i32 when it should be usize, why wouldn't it make the same mistake for variables defined using let?

Inference can only occur when the code that is using a value actually uses it, without using a value its type will be difficult to infer. After all, the compiler can only guess a type for a value that's never used.

For example, if you write: let x = 42, the compiler infers the type of that integer from the way in which it is subsequently used. If the code using the value expects that value to be used in a way that implies it should be of type u8, then the compiler will infer that x is of type u8. Likewise, if the code expects a value of type u32, then the compiler will infer that the value is of type u32.

However for both let and const values, if there are multiple possible types for the value then the compiler will demand that you specify a type, which is understandable.

However, why enforce strict type annotations at all times for constants when the compiler can infer types for both mutable and immutable values in certain scenarios?

Other inconsistencies

Another inconsistency is the strict requirement for type annotations on global let variables but not local let variables that don't leave their host scope. This is understandable for public and/or global data, as it makes sense to enforce strict type annotations to prevent type errors from occurring in other source files, but enforcing different scope based rules for mutable and immutable variables is an unnecessary complication.

Potential solutions

Based on the points made above, the rules could be standardised for let, const and static variables type annotations as follows:

Closing thoughts

Like many, I enjoy working with Rust and regularly use it. I wrote this opinion piece not to bash the language but to discuss my opinions. While I acknowledge my bias in favour of these arguments, I also understand what happens when established languages make breaking changes.

As always, take other people's opinions with a pinch of salt and do your research before forming your own.

Thank you for reading.


Note: References to Python Docs & the StackOverflow Developer Survey are not used to provide Rust programming information.