- Rust is a statically typed language; it checks data types at compile-time. But it doesnβt require you to actually type data types when declaring variable bindings. In that case, the compiler checks the usage and sets a better data type for it.
- βοΈ For constants and statics, we must annotate the data type.
- Types come after a
:(colon) sign. - The naming convention for the variable bindings is using the
snake_case. But, for constants and statics, we should follow theSCREAMING_SNAKE_CASE.
π In the following examples, we will use data types like
bool,i32,i64andf64. Don’t worry about them for now; they’ll be discussed later.
Variable Bindings
The
letkeyword is used in binding expressions. We can bind a name to a value or a function. Also, because the left-hand side of a let expression is a “pattern”, you can bind multiple names to a set of values or function values.In Rust, variables are immutable by default, so we call them Variable bindings. To make them mutable, the
mutkeyword is used.
Declaration & Assignment
let a; // Declaration; without data type
a = 5; // Assignment
let b: i8; // Declaration; with data type
b = 5;
let c = true; // Declaration + assignment; without data type
let d: bool = false; // Declaration + assignment; with data type
let e = 4 + 2; // e = 6
Mutability
As mentioned, variable bindings are immutable by default. We need to add the mut keyword to make them mutable.
let mut a = 5; // a = 5
a = a + 5; // a = 10
Multiple Declarations & Assignments
let (a, b); // Declaration
(a, b) = (1, 2); // Assignment
// Declaration + assignment
let (a, b) = (1, 2); // a = 1 and b = 2
// Declaration + assignment + mutability
let (mut a, mut b) = (3, 4); // a = 3 and b = 4
(a, b) = (a-b, a+b); // a = -1 and b = 7
Scope
let (a, b) = (1, 2); // a = 1 and b = 2
let c = {
let a = 4; // affects inside wrapping {} only
let b = 6; // affects inside wrapping {} only
a + b
}; // c = 10
let d = { a + b }; // d = 3
println!("{a} {b} {c} {d}"); // 1 2 10 3
Constants
The const keyword is used to define constants and after the assignment their values are not allowed to change. They live for the entire lifetime of a program but has no fixed address in the memory.
const N: i32 = 5;
const DB_PORT: u16 = 5432;
const SERVER_TIMEOUT: u32 = 60 * 5;
Statics
The static keyword is used to define a “global variable” type facility. There is only one instance for each value, and itβs at a fixed location in memory.
static N: i32 = 5;
static DB_PORT: u16 = 5432;
static SERVER_TIMEOUT: u32 = 60 * 5;
π While you need constants, always use
const, instead ofstatic. Itβs pretty rare that you actually want a memory location associated with your constant, and using a const allows for optimizations like constant propagation, not only in your crate but also in downstream crates.
Variable Shadowing
Sometimes, while dealing with data, initially we get them in one unit but need to transform them into another unit for further processing. In this situation, instead of using different variable names, Rust allows us to redeclare the same variable with a different data type and/ or with a different mutability setting. We call this Shadowing.
let s: &str = "hello"; // &str
let s: String = s.to_uppercase(); // String
println!("{s}"); // HELLO
let (a, b) = (1, 2);
let (a, b) = (b, a); // swap variables via shadowing
println!("{a} {b}"); // 2 1
fn main() {
let a: f64 = -20.48; // float
let a: i64 = a.floor() as i64; // int
println!("{a}"); // -21
{
let a = a + 26; // affects inside wrapping {} scope only
println!("{a}"); // 5 π‘ -21 + 26
}
println!("{a}"); // -21 π‘ outer a
}
π¨βπ« Before going to the next…
Usually, constants and statics are placed at the top of the code file, outside the functions (after module imports/
usedeclarations).const PI: f64 = 3.14159265359; fn main() { println!("Ο value is {}", PI); }