RUST : 3. Common Programming Concepts
Variables and Mutability
Default variables are immutable.
If you want mutable variable, can make them mutable by adding mut in front of the variable name.
fn main() {
let mut x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
First, you aren’t allowed to use mut with constants. Constants aren’t just immutable by default—they’re always immutable.
You declare constants using the const keyword instead of the let keyword, and the type of the value must be annotated.
const MAX_POINTS: u32 = 100_000;
Note: Rust’s naming convention for constants is to use all uppercase with underscores between words, and underscores can be inserted in numeric literals to improve readability
Data Types
Keep in mind that Rust is a statically typed language, which means that it must know the types of all variables at compile time.
Table 3-1: Integer Types in Rust
| Length | Signed | Unsigned |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| arch | isize | usize |
Table 3-2:Integer Literals in Rust
| Number literals | Example |
|---|---|
| Decimal | 98_222 |
| Hex | 0xff |
| Octal | 0o77 |
| Binary | 0b1111_0000 |
| Byte (u8 only) | b’A’ |
// Floating-Point(default type is f64)
let x = 2.0; // f64
let y: f32 = 3.0; // f32
// Numeric Operations
let sum = 5 + 10;
let difference = 95.5 - 4.3;
let product = 4 * 30;
let quotient = 56.7 / 32.2;
let remainder = 43 % 5;
// Boolean
let t = true;
let f: bool = false; // with explicit type annotation
// Character
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
// Tuple
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
// Access a tuple element directly by using a period(.)
// followed by the index of the value we want to access
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
// array
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
Functions
fn keyword, which allows you to declare new functions.
Rust doesn’t care where you define your functions, only that they’re defined somewhere.
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
Parameters
fn main() {
another_function(5, 6);
}
fn another_function(x: i32, y: i32) {
println!("The value of x is: {}", x);
println!("The value of y is: {}", y);
}
Function Bodies Contain Statements and Expressions
Statements are instructions that perform some action and do not return a value.
Expressions evaluate to a resulting value.
fn main() {
let y = 6; // This is Statemnets
// Error - (let y = 6) statement does not return a value
let x = (let y = 6);
}
Note: This is different from what happens in other languages, such as C and Ruby, where the assignment returns the value of the assignment. In those languages, you can write x = y = 6 and have both x and y have the value 6; that is not the case in Rust.
Expressions can be part of statements: the 6 in the statement let y = 6; is an expression that evaluates to the value 6.
Calling a function is an expression. Calling a macro is an expression. The block that we use to create new scopes, {}, is an expression, for example:
fn main() {
let x = 5;
let y = { //like this
let x = 3;
x + 1
};
println!("The value of y is: {}", y);
}
This expression:
{
let x = 3;
x + 1
}
Note: the x + 1 line without a semicolon at the end, which is unlike most of the lines you’ve seen so far.
Expressions do not include ending semicolons. If you add a semicolon to the end of an expression, you turn it into a statement, which will then not return a value. Keep this in mind as you explore function return values and expressions next.
Functions with Return Values
We don’t name return values, but we do declare their type after an arrow (->) In Rust, the return value of the function is synonymous with the value of the final expression in the block of the body of a function. You can return early from a function by using the return keyword and specifying a value, but most functions return the last expression implicitly.
// perfectly valid function in Rust
fn five() -> i32 {
5
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
}
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1
// If place a semicolon at the end of the line x + 1,
// changing it from an expression to a statement,
// we’ll get an error.
// like this -> error[E0308]: mismatched types
}
Comments
A simple comment:
// So we’re doing something complicated here,
// long enough that we need
// multiple lines of comments to do it!
// Whew! Hopefully, this comment will
// explain what’s going on.
let lucky_number = 7; // I’m feeling lucky today.
Rust also has another kind of comment, documentation comments, which we’ll discuss in the “Publishing a Crate to Crates.io” section of Chapter 14.
Control Flow
The most common constructs that let you control the flow of execution of Rust code are if expressions and loops.
if Expressions
fn main() {
let number = 3;
if number < 5 {
rintln!("condition was true");
} else {
println!("condition was false");
}
}
It’s also worth noting that the condition in this code must be a bool. Rust will not automatically try to convert non-Boolean types to a Boolean.
fn main() {
let number = 3;
if number { //Change to "if number != 0 {"
println!("number was three");
}
}
You can have multiple conditions by combining if and else in an else if expression.
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
Because if is an expression, we can use it on the right side of a let statement, as in below.
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
}
loop Expressions
The loop keyword tells Rust to execute a block of code over and over again forever or until you explicitly tell it to stop.
Most terminals support a keyboard shortcut, ctrl-c, to interrupt a program that is stuck in a continual loop. The symbol ^C represents where you pressed ctrl-c. You may or may not see the word again! printed after the ^C, depending on where the code was in the loop when it received the interrupt signal.
fn main() {
loop {
println!("again!");
}
}
// Returning Values from Loops
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
while Expressions
While the condition is true, the loop runs. When the condition ceases to be true, the program calls break, stopping the loop. This loop type could be implemented using a combination of loop, if, else, and break; you could try that now in a program, if you’d like.
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
for Expressions
You could use the while construct to loop over the elements of a collection, such as an array.
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index = index + 1;
}
}
But this approach is error prone; we could cause the program to panic if the index length is incorrect. It’s also slow, because the compiler adds runtime code to perform the conditional check on every element on every iteration through the loop. As a more concise alternative, you can use a for loop and execute some code for each item in a collection.
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
}
Here’s what the countdown would look like using a for loop and another method we’ve not yet talked about, rev, to reverse the range:
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}