Rust already amazes a lot when it comes to the basic language features. Let’s, for example, take a look at shadowing and temporary mutability. Both, fairly basic and simple language features, yet powerful and a real booster for robustness and correctness.
Shadowing in Rust
Shadowing is such a simple language feature. However, it has a significant effect on the code you write daily. Shadowing allows you to re-declare a variable in the same scope, using the same name. The re-declared variable differs from the original by having a different type. This is especially useful upon casting data from one type into another. In Rust, you can achieve this without adding unnecessary application-state.Take the following code as an example:
let mut number = String::new();
io::stdin().read_line(&mut number)?;
let number = number.trim().parse::<i32>()?;
The Rust compiler (rustc
) recognizes the shadowing operation and guarantees that ongoing code is (in this example) dealing with an i32
instead of String
. Although, same the same logic requires less typing in other languages (such as C#), I like the fact that I don’t have to add application state.
// C#
var number = Console.ReadLine();
var numberAsInt = Int32.Parse(number.Trim());
Shadowing and Scopes in Rust
Shadowing also plays nicely with nested scopes in Rust. It makes the code even more readable and adds the possibility of “falling back” to the initial type/value of the shadowed variable. Take a look at the following example. The firstname
variable will be shadowed in a dedicated scope. After leaving the scope, the associated stack will is destroyed, and the original firstname
variable is used again.
fn main() {
let lastname = "Smith";
let firstname = "John";
println!("My name is {}, {} {}", lastname, firstname, lastname);
{
println!("> My name is {}, {} {}", lastname, firstname, lastname);
let firstname = "Mike";
println!("> My name is {}, {} {}", lastname, firstname, lastname);
}
println!("My name is {}, {} {}", lastname, firstname, lastname);
/* output:
My name is Smith, John Smith
> My name is Smith, John Smith
> My name is Smith, Mike Smith
My name is Smith, John Smith
*/
}
Thanks to minigamedev for suggesting this one.
Temporary mutability in Rust
Variables in Rust are immutable by default. That said, you can’t mutate the value of a variable once it’s initialized. Your application will not compile.
fn immutable_name() {
let name = "John";
println!("Hello, {}", name);
// prints Hello, John
name = "Mike";
// will not compile, because name is immutable
}
fn immutable_name_long_version() {
let name: &str;
name = "John";
println!("Hello, {}", name);
// prints Hello, John
name = "Mike";
// will not compile, because name is immutable
}
However, you can make any variable mutable by adding the mut
keyword upon variable declaration:
fn mutable_age() -> u16 {
let mut age = 35;
// works because age is mutable
age = 36;
age
}
The variable age
in the previous example is immutable for the entire scope. By re-declaring the age
variable as immutable, the Rust compiler can prevent us from accidentally doing state mutation if not intended:
fn temp_mutable_age() -> u16 {
let mut age = 30;
// age is mutable
age = 40;
let age = age;
// age is immutable
// age = 50 // 👈🏻 would not compile, because age is immutable at this point!
age
}
Conclusion
So far, learning Rust is fun. It’s refreshing, also challenging when it comes to more complex language characteristics. Finding small nuggets like Shadowing and Temporary Mutability increases the fun and makes me look more and more into the language, its standard library, and the ecosystem.