Based on “The Rust Programming”
Variables and Mutability
- Constant are like immutable varibles, but always immutable.
- declare constants using the
const
keyword, and the type of the value must be annotated. - constants can be declared in any scope, including the global scope.
- constants may be set only to a constant expression, not the result of a value that could only be computed at runtime.
- Rust’s naming convention for constants is to use all uppercase with underscores between words, such as
THREE_HOURS_IN_SECONDS
- the compiler is able to evaluate a limited set of operations at compile time, which lets us choose to write out this value in a way that’s easier to understand and verify
- Shadow is different from a variable marked as
mut
- will get a compile-time error if we accidentally try to reassign to this variable without using the
let
keyword. - shadowing is effectively creating a new variable when we use the
let
keyword again
- will get a compile-time error if we accidentally try to reassign to this variable without using the
- we’re not allowed to mutate a variable’s type
- the compiler can usually infer what type we want to use based on the value and how we use it. In cases when many types are possible, we must add a type annotation
Example Code:
fn main() {
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
//mutable variable
let mut x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
let y = 5;
let y = y + 1;
{
let y = y * 2;
println!("The value of xy in the inner scope is: {}", y);
}
println!("The value of y is: {}", y);
}
Data Types
Scalar types, represents a single value:
Interger:
Length Signed Unsigned 8-bit i8 u8 16-bit i16 u16 32-bit i32 u32 64-bit i64 u64 128-bit i128 u128 arch isize usize - Each signed variant can store numbers from -(2^(n - 1)) to 2^(n - 1) - 1 inclusive, where n is the number of bits that variant uses.
- arch depend on the architecture of the computer your program is running on
- you can write integer literals in any of the forms:
Number literals Example Decimal 98_222 Hex 0xff Octal 0o77 Binary 0b1111_0000 Byte ( u8
only)b’A' - number literals that can be multiple numeric types allow a type suffix, such as
57u8
, to designate the type. - Number literals can also use
_
as a visual separator to make the number easier to read,1_100
is the same value as1000
- rust integer types default to
i32
- When compiling in release mode with the
--release
flag, Rust does not include checks for integer overflow that cause panics. Instead, if overflow occurs, Rust performs two’s complement wrapping. In short, values greater than the maximum value the type can hold “wrap around” to the minimum of the values the type can hold. In the case of au8
, the value 256 becomes 0, the value 257 becomes 1, and so on. The program won’t panic, but the variable will have a value that probably isn’t what you were expecting it to have.
Floating-Point:
f32
is 32 bits in size, single-precision floatf64
is 64 bits in size, double-precision float- rust floating-point types default to
f64
- floating-point numbers are represented according to the IEEE-754 standard
Boolean:
true
orfalse
- 1 byte in size
- specified using
bool
keyword
Character:
- specified using
char
keyword, with single quotes - 4 bytes in size, represents a Unicode Scalar Value, lot more than just ASCII, including Accented letters, CJK charaters, emoji, zero-with spaces.
- Unicode range from
U+0000
-U+D7FF
, andU+E000
-U+10FFFF
inclusive
- specified using
Compound types, grouping multiple values into one type:
Tuple
- a general way of grouping together a number of values with a variety of types into one compound type
- fixed length, once declared, cannot grow or shrink in size,
fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); }
- the variable
tup
binds to the entire tuple - use pattern matching to destructure a tuple value
fn main() { let tup = (500, 6.4, 1); let (x, y, z) = tup; println!("The value of y is: {}", y); }
- we can also access a tuple element directly by using a period
.
followed by the index of the value we want to access.fn main() { let x: (i32, f64, u8) = (500, 6.4, 1); let five_hundred = x.0; let six_point_four = x.1; let one = x.2; }
- the tuple without any values
()
, is a special type that has only one value, also written()
. The type is called the unit type and the value is called the unit value. Expressions implicitly return the unit value if they don’t return any other value.
Array
- elements must have the same type.
- length of the array is fixed in rust.
- useful when you want your data allocated on the stack rather than the heap.
- arrays are more useful when you know the number of elements will not need to change.
// creating array let a = [1, 2, 3, 4, 5]; // indicate type and size let a: [i32; 5] = [1, 2, 3, 4, 5]; // initialize the same value for all elements let a = [3; 5]; // is the same as let a = [3, 3, 3, 3, 3];
- When trying to access element out of the bound, Rust’s memory safety principles will be in action, protects you against this kind of error by immediately exiting instead of allowing the memory access and continuing.
Functions
- Rust code uses snake case as the conventional style for function and variable names.
- Function can have parameters, you can provide it with concrete values(arguments) for those parameters.
- In function signatures, you must declare the type of each parameter.
fn main() { print_labeled_measurement(5, 'kg'); } fn print_labeled_measurement(value: i32, unit_label: char) { println!("The measurement is: {}{}", value, unit_label); }
- Rust is an expression-based language.
- Statements are instructions that perform some action and do not return a value.
- Expressions evaluate to a resulting value.
- The following will fail at compile, because (
let y = 6
) does not return a value:Calling a function is an expression. Calling a macro is an expression. A new scope block created with curly brackets is an expression, for example:fn main() { let x = (let y = 6); }
fn main() { let y = { let x = 3; x + 1 }; println!("The value of y is: {}", y); }
- We don’t name return values, but we must declare their type after an arrow (
->
). - You can return early from a function by using the
return
keyword and specifying a value, but most functions return the last expression implicitly. - Example:
fn five() -> i32 { 5 } fn main() { let x = five(); println!("The value of x is: {}", x); }
Comments
- In Rust, the idiomatic comment style starts a comment with two slashes, and the comment continues until the end of the line.
- For multiline comments , you’ll need to include
//
on each line.// This is a comment // A very very very very very very very long comment
- Rust also has another kind of comment, documentation comments.
Control Flow
if expression
- Blocks of code associated with the conditions in
if
expressions are sometimes called arms, just like the arms inmatch
expressions. - Using too many else
if
expressions can clutter your code, so if you have more than one, you might want to refactor your code, a powerful Rust branching construct calledmatch
for these cases. - Because
if
is an expression, we can use it on the right side of alet
statement to assign the outcome to a variable, example:it will outputfn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {}", number); }
The value of number is: 5
. - But if in the following example, we’ll get an error:This won’t work because variables must have a single type, and Rust needs to know at compile time what type the number variable is, definitively.
fn main() { let condition = true; let number = if condition { 5 } else { "six" }; println!("The value of number is: {}", number); }
- The the values that have the potential to be results from each arm of the
if
must be the same type.
- Blocks of code associated with the conditions in
Repetition with Loops
loop
- The
loop
keyword tells Rust to execute a block of code over and over again forever or until you explicitly tell it to stop. - Place the
break
keyword within the loop to tell the program when to stop executing the loop. - Use
continue
in a loop tells the program to skip over any remaining code in this iteration of the loop and go to the next iteration. - If you have loops within loops,
break
andcontinue
apply to the innermost loop at that point. - To pass the result of that operation out of the loop to the rest of your code, add the value you want returned after the
break
expression you use to stop the loop; that value will be returned out of the loop so you can use it, example:this will outputfn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("The result is {}", result); }
The result is 20
.
- The
while
- Using a
while
loop to run code while a condition holds true:fn main() { let mut number = 3; while number != 0 { println!("{}!", number); number -= 1; } println!("START!!"); }
- Using a
for
- use a
for
loop and execute some code for each item in a collection. - In situations in which you want to run some code a certain number of times, most Rustaceans would use a
for
loop. - The way to do that would be to use a
Range
, provided by the standard library, which generates all numbers in sequence starting from one number and ending before another number. rev
, to reverse the rangefn main() { for number in (1..4).rev() { println!("{}!", number); } println!("LIFTOFF!!!"); }
- use a
Example Code:
fn main() { let mut count = 0; //The outer loop has the label 'counting_up, and it will count up from 0 to 2. 'counting_up: loop { println!("count = {}", count); let mut remaining = 10; // The inner loop without a label counts down from 10 to 9. loop { println!("remaining = {}", remaining); if remaining == 9 { break; //The first break that doesn’t specify a label will exit the inner loop only. } if count == 2 { break 'counting_up; // The break 'counting_up; statement will exit the outer loop. This code prints: } remaining -= 1; } count += 1; } println!("End count = {}", count); }