Common Programming Concepts
📚 Chapter 3
This Chapter introduces common programming concepts in Rust, such as variables, data types, functions, statements and expressions, comments, and control flow.
You can skip this Chapter if you already have experience with using let
, let mut
, const
, i32
, f64
, bool
, char
, Tuple
, Array
, fn
, if
, else
, loop
, while
, and for
in Rust.
Variables and Mutability
Variables in Rust are declared using the let
keyword. By default, variables are immutable, which means that once a value is assigned to a variable, it cannot be changed.
let x = 5;
x = 6; // This will cause a compile-time error
To make a variable mutable, you need to use the mut
keyword.
let mut x = 5;
x = 6; // This is allowed
Shadowing
Shadowing is when you declare a new variable with the same name as a previous variable. The new variable shadows the previous variable.
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {}", x); // x = 12
}
println!("The value of x in the outer scope is: {}", x); // x = 6
The scope of the variable x
in the inner block is limited to that block. The variable x
in the outer block is not affected by the inner block.
Shadowing is different from using mut
because we can change the type of the variable using shadowing.
let digits = "123"; // digits is a string
let digits = digits.len(); // digits is an integer, the length of the string
Constants
Constants are declared using the const
keyword. Constants are always immutable, and their type must be annotated. Constants can not be shadowed.
const MAX_POINTS: u32 = 100_000;
Concept Check Quiz 1
What is the output of the following code snippet?
let password = "0123";
let password = password.len();
println!("{}", password);
Show solution
4. The code snippet compiles and outputs 4
. The variable password
is shadowed by redeclaring it with a different type.
What is the output of the following code snippet?
let mut x = 1;
x = 2;
{
let x = x + 1;
}
println!("{}", x);
Show solution
2. The code snippet compiles and outputs 2
. The variable x
is shadowed in the inner block and does not affect the outer block.
What is the output of the following code snippet?
let mut x = 1;
x = "2";
println!("{}", x);
Show solution
Does not compile. The code snippet does not compile because the variable x
is declared as an integer and then assigned a string value.
Data Types
Rust is a statically typed language, which means that the type of every variable must be known at compile time. There are two categories of data types in Rust: scalar and compound.
Scalar Types
Scalar types in Rust represent single values. Rust's primary scalar types include integers, floating-point numbers, booleans, and characters.
Integers
Integers are whole numbers without a fractional component. Rust provides signed and unsigned integers of different sizes. For example, i8
and u8
represent signed and unsigned 8-bit integers, respectively. Each signed integer type can store numbers from i32
.
let x: i8; // range: [-2^7, 2^7 - 1] => [-128, 127]
let x: u8; // range: [0, 2^8 - 1] => [0, 255]
let x: i16; // range: [-2^15, 2^15 - 1] => [-32,768, 32,767]
let x: u16; // range: [0, 2^16 - 1] => [0, 65,535]
let x: i32; // range: [-2^31, 2^31 - 1] => [-2,147,483,648, 2,147,483,647]
let x: u32; // range: [0, 2^32 - 1] => [0, 4,294,967,295]
// More built-in integer types: i64, u64, i128, u128, isize, usize
// See https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-types
When an integer overflows, Rust will panic (crash) at runtime in debug mode. In release mode, Rust will perform two's complement wrapping, which means that the value will wrap around to the minimum value of the integer type, for example, 255 + 1 = 0
for an 8-bit unsigned integer.
let x: u8 = 255;
let y = x + 1; // This will panic in debug mode, wrap to 0 in release mode
println!("y = {}", y);
To explicitly handle integer overflow, you can use checked arithmetic, which returns an Option
type. The checked_add
method returns Some(result)
if the operation does not overflow, and None
if it does.
let x: u8 = 255;
let y = x.checked_add(1);
match y {
Some(v) => println!("Result: {}", v),
None => println!("Overflow!"),
}
More information about integer overflow can be found in the official Rust book: Integer Overflow
Floating-Point Numbers
Floating-point numbers represent numbers with a fractional component. Rust has two primitive floating-point types: f32
and f64
, which are 32-bit and 64-bit floating-point numbers, respectively. The IEEE-754 standard defines the representation of floating-point numbers. The default floating-point type is f64
.
Rust supports basic arithmetic operations on floating-point and integer numbers, such as addition, subtraction, multiplication, and division.
let x: f32 = 3.14;
let y: f32 = 1.0;
let sum = x + y; // addition
let diff = x - y; // subtraction
let prod = x * y; // multiplication
let quot = 10 / 3; // division
let rem = 10 % 3; // remainder
Booleans
Booleans represent logical values: true
and false
. Booleans are one byte in size.
let x: bool = true;
let y: bool = false;
Characters
Characters represent single Unicode characters and are enclosed in single quotes. Rust's char
type is four bytes in size and can represent any Unicode scalar value, including emojis.
let x: char = 'A';
let y: char = '😀';
Compound Types
Compound types in Rust combine multiple values into a single type. Rust's primary compound types are tuples and arrays.
Tuples
Tuples are collections of values, and each value in a tuple can have a different type. Tuples have a fixed length, and their elements can be accessed by index.
let tuple: (i32, i32, char) = (1, 2, '3');
let (x, y, z) = tuple; // destructuring
println!("x: {}, y: {}, z: {}", x, y, z);
let first = tuple.0; // accessing elements by index
let second = tuple.1;
let third = tuple.2;
println!("first: {}, second: {}, third: {}", first, second, third);
Arrays
Arrays are collections of values of the same type with a fixed length. Arrays in Rust are stack-allocated and have a fixed size, which is determined at compile time. The type of an array includes the element type and the number of elements.
let array: [i32; 3] = [1, 2, 3]; // array of 3 integers
let first = array[0]; // accessing elements by index
let second = array[1];
let third = array[2];
println!("first: {}, second: {}, third: {}", first, second, third);
Rust provides a shorthand syntax for initializing arrays with the same value.
let array = [0; 3]; // array of 3 zeros
Arrays in Rust are bounds-checked at runtime. If you try to access an element outside the bounds of the array, Rust will panic.
let array = [1, 2, 3];
let fourth = array[3]; // This will panic
The length of an array can be obtained using the len
method.
let array = [1, 2, 3];
let length = array.len(); // length = 3
To avoid panicking, you can use the get
method, which returns an Option
type.
let array = [1, 2, 3];
let fourth = array.get(3);
match fourth {
Some(v) => println!("Fourth element: {}", v),
None => println!("Index out of bounds"),
}
Concept Check Quiz 2
True or False: Scalar types in Rust represent single values while compound types combine multiple values into a single type.
Show solution
True. Scalar types include integers, floating-point numbers, booleans, and characters, while compound types include tuples and arrays.
What is the output of the following code snippet in debug mode?
let x: u8 = 255;
let y = x + 1;
println!("{}", y);
Show solution
Does not compile. The code snippet does not compile because the addition operation overflows the u8
integer type. By default, Rust does not allow code to compile that results in an integer overflow and will panic at runtime in debug mode.
What is the output of the following code snippet?
let array = [1; 3];
let tuple = (1, array);
let x = tuple.0 + tuple.1[0];
println!("{}", x);
Show solution
2. The code snippet compiles and outputs 2
. The tuple contains an integer and an array, and the sum of the first element of the tuple and the first element of the array is calculated. [1; 3]
creates an array of 3 elements with the value 1
.
Functions
Functions in Rust are declared using the fn
keyword. The main function is the entry point of a Rust program and is called when the program is executed.
fn main() {
println!("Hello, world!");
}
Functions can take parameters, which are specified within parentheses after the function name. The type of each parameter must be annotated. If a function returns a value, the return type must be specified after an arrow ->
.
fn add(x: i32, y: i32) -> i32 {
x + y // The last expression in a function is the return value
}
Statements and Expressions
Rust is an expression-based language, which means that most constructs in Rust are expressions that return a value. Expressions evaluate to a value, while statements perform actions and do not return a value.
let x = 1; // statement
let y = { // block expression
let z = 2; // statement
z + 1 // expression
};
println!("{}", y); // 3
Concept Check Quiz 3
What is the output of the following code snippet?
fn add_one(x: i32) {
x + 1
}
let sum = add_one(1);
println!("{}", sum);
Show solution
Does not compile. The code snippet does not compile because the type of the parameter x
is not annotated in the function signature.
Comments
Comments in Rust are created using //
for single-line comments and /* */
for multi-line comments. Comments are ignored by the compiler and are used to document code.
// This is a single-line comment
/*
* This is a multi-line comment
* that spans multiple lines
*/
let x = 1; // This is an inline comment
Control Flow
Rust provides control flow constructs such as if
, else
, loop
, while
, and for
to control the flow of execution in a program.
if
Expressions
The if
expression evaluates a condition and executes a block of code if the condition is true.
let x = 1;
if x > 0 {
println!("x is positive");
} else if x < 0 {
println!("x is negative");
} else {
println!("x is zero");
}
The if
expression can be used in a let statement to assign a value based on a condition.
let x = 1;
let y = if x > 0 { 1 } else { -1 };
println!("y = {}", y); // y = 1
Rust does not have a concept of "Truthy" like some other languages. The condition in an if
expression must be a boolean value.
let x = 1;
if x { // This will not compile
println!("x is true");
}
loop
Loops
The loop
keyword creates an infinite loop that continues until explicitly stopped using the break
keyword.
let mut x = 1;
loop {
x *= 2;
if x > 100 {
break;
}
}
println!("x = {}", x); // x = 128
break
can be used to return a value from a loop.
let mut x = 1;
let y = loop {
x *= 2;
if x > 100 {
break x - 100;
}
};
println!("y = {}", y); // y = 28
Nested loops can be labeled to break out of a specific loop.
let condition = true;
'outer: loop {
'inner: loop {
if condition {
break 'outer;
}
}
}
continue
can be used to skip the rest of the loop and start the next iteration.
let mut x = 1;
loop {
x += 1;
if x % 2 == 0 {
continue;
}
if x > 10 {
break;
}
println!("{}", x); // Output: 3, 5, 7, 9
}
while
Loops
The while
keyword creates a loop that continues as long as a condition is true.
let mut x = 1;
while x < 100 {
x *= 2;
}
println!("x = {}", x); // x = 128
for
Loops
The for
keyword is used to iterate over a collection of items, such as an array or a range.
let array = [1, 2, 3];
for element in array {
println!("{}", element); // Output: 1, 2, 3
}
for i in 0..array.len() {
println!("{}", i); // Output: 0, 1, 2
}
Concept Check Quiz 4
What is the output of the following code snippet?
let x = 0;
let y = if !x { 1 } else { -1 };
println!("{}", y);
Show solution
Does not compile. The code snippet does not compile because the condition in the if
expression must be a boolean value.
True or False: The following code snippet runs an infinite loop.
let condition = true;
'outer: loop {
'inner: loop {
if condition {
break 'outer;
}
}
}
Show solution
False. The code snippet does not run an infinite loop because the break 'outer;
statement breaks out of the outer loop when the condition is true.
Progress on this Page
References
- Read this Chapter on the official Rust book: Common Programming Concepts - The Rust Programming Language
- Operators and Symbols in Rust: Operators and Symbols
- Rust has a set of Keywords reserved for the language: Appendix A: Keywords