Rust Language Tutorial
Hello! And welcome to our main course on the Rust programming language.
This course is beginner-friendly and covers beginner through intermediate-level topics in Rust programming. It covers everything from the absolute basics – including variable declarations, functions, conditionals, and loops – all the way through more advanced topics like traits, generics, ownership, and lifetimes.
This page serves as both a standalone, single-page tutorial of Rust, as well as an index of the complete course.
You can read through this entire page, or you can dive into the details of any of the topics covered in the course via the links below (or to the left on desktop/tablet).
A Complete Course on Rust Programming
The course itself is comprised of nearly 100 individual tutorials that cover the details of programming in Rust. It’s designed to be beginner friendly and increases in complexity as you go through it.
Unlike many other courses and resources for learning Rust, this course doesn’t assume prior programming knowledge. It’s designed to make learning Rustlang as accessible and fun as possible.
If you do have programming experience, feel free to look through the overview covered on this page to get a feel for Rust syntax and the stuff that helps make Rust ridiculously fast and safe. If you’re completely new to programming, it’s probably best to start at the beginning.
Enough preamble; let’s get our Rust on!
Learn Rust in One Page
This is an introduction to Rust that covers all of the basics, from data types and loops to lifetimes and
ownership. You can read it through to the end or jump into the details of any topic using the links provided in each section.
Of course, it’s impossible to learn an entire programming language in a single tutorial unless that tutorial happens to be the length of several books. But it is possible to gain a sense of understanding about a language’s features and syntax without in a short period of time.
What is Rust?
Rust is a modern, open-source programming language designed for safety, speed, and concurrency. It was created by Mozilla in 2010 and is now maintained by a community of developers.
Rust strives to provide the incredible performance of low-level languages like C and C++ while offering strong memory safety features that prevent common programming errors. It achieves this through a combination of its ownership system, which ensures that each value has a single owner responsible for managing its’ lifetime, as well as the Rust borrow checker, which enforces safe usage of shared references to data.
Rust is used in a variety of applications, including systems programming, web development, game development, and more.
Start Coding With Rust
You can quickly get started with Rust using a playground like this one, which will work for simple examples and learning the language in general. Most of the code in this course can be copied and pasted into a playground and should compile without errors (except for the examples demonstrating errors).
However in order to build real-world applications, you will need to install Rust and run it on your local machine.
Main article => Getting Started With Rust
Writing Your First Rust Program
As with other languages, it is customary to start programming in Rust using the “Hello, World!” program. The linked tutorial covers how to run this on a local machine but we can learn a great deal just by looking at the code:
fn main() {
println!("Hello, world!");
}
Standard Output:
Hello, world!
There are a few important points here:
First, Rust uses the main() function to provide control flow for the program. In other words, the main() function tells the compiler what to execute. Functions outside the main() function aren’t stored in memory until they are called by the main() function.
Second, we print to the terminal using the println! macro. This macro prints the text as well as a new line (the print! macro can be used to print without a new line).
We will cover the details of how this all works below and in the main course.
Main article => “Hello, World!” in Rust
Printing
Rust provides four handy macros for printing. We can print to either the standard output or standard error, and we can choose to append a new line or not.
fn main() {
print!("Prints to stdout");
println!("Prints to stdout with new line");
eprint!("Prints to stderr");
eprintln!("Prints to stderr with new line");
}
Standard Error:
Prints to stderrPrints to stderr with new line
Standard Output:
Prints to stdoutPrints to stdout with new line
Of these four macros, println! is the most commonly used; we’ll see it in almost every example going forward. Printing with a new line appended helps make the code cleaner than in the example above.
If you’re confused about stdout vs. stderr, check out the main tutorial on printing in Rust.
Main article => Printing in Rust
Comments
As with many other languages, Rust supports both single and multi-line commenting. Single lines can be commented out using two forward slashes // and multiple lines can be commented using a slash asterisk: /* */.
fn main() {
// This is a single line comment
/* This
is
a
multi-line
comment
*/
}
Comments are useful not only to annotate code but also to comment out code while programming and debugging.
Main article => Comments
Constants
Constants are unique in Rust when compared with other languages because they aren’t simply immutable variables. All variables in Rust are immutable by default, but can be made mutable using the mut keyword (more on this below).
Immutability is a feature of constants but unlike immutable variables, constants can have a global scope, while variables are limited only to local scope. This means that constants can be declared outside of the main() function.
Constants are declared using the keyword const and the data type must be annotated:
const MY_CONST: i32 = 42;
Note that constants are named using SCREAMING_SNAKE_CASE by convention.
Main article => Constants
Variables
Variables consist of a value that is bound to a name using the keyword let:
let my_var = "Hello!";
This basic functionality is similar to variables in other languages, but variables in Rust have some unique features.
As mentioned in the previous section (on constants), variables are immutable by default. They can be made mutable using the keyword mut. Once mutable, a new value can be assigned:
let mut my_var = "Hello!";
my_var = "Hello, World!";
In addition, variables are subject both to shadowing as well as strict rules regarding scope.
These rules are designed to work with the ownership system in order to extend the features of variables while maintaining a high level of memory and type safety.
Main article => Variables
Variable Scope
Variable scope refers to the part of the code over which a variable is valid and can be accessed. Scope is determined by the location of the variable declaration, and variables can’t be accessed outside of their scope.
Variables are limited to a local scope in Rust, while constants can have a global scope. In general, variable scope is determined by the code block in which the variable is declared. The code block is determined by curly braces; variable scope starts when the variable is declared and ends with the curly braces defining the code block
{
let my_var = 42; // Scope of my_var starts here
} // Scope of my_var ends here
Note that this rule also applies for code blocks defined by functions.
Main article => Variable Scope
Variable Shadowing
When one variable is declared within the inner scope of a variable of the same name, the inner scoped variable shadows the outer scoped variable. This means that the inner-scoped variable can take one a different value without changing the value of the value of the outer scoped variable.
// my_var declared in outer scope:
let my_var = 21;
{
// my_var redeclared in outer scope:
let my_var = 42;
println!("{}", my_var); // Prints '42'
}
println!("{}", my_var) // Prints '21' because outer scoped variable was shadowed
Main article => Variable shadowing
Data Types
Rust comes with a number of helpful data types, and custom types can also be created. Default data types include:
We’ll cover an overview of the main categories below.
Main article => Data Types
Numbers
Rust has support for both integers and floating point numbers (floats). Integers are whole numbers without a decimal point, while floats must have a decimal.
Integers can be either signed or unsigned. Signed integers always have a positive ‘sign’; meaning that they are always positive in value. Unsigned integers can have positive or negative values.
The possible size of any number is dictated by the number of bits as well as the type. Number type is abbreviated by a letter followed by the number of bits: i32 signifies a 32 bit signed integer, u32 indicated a 32 bit unsigned integer, and f32 indicates a 32-bit floating point number.
There are two types of floating-point numbers in Rust: f32 and f64. 64-bit floats allow greater precision when working with decimals and reduce rounding errors as well.
fn main() {
let a = 3.0;
let b = 2.5;
println!("Float addition: {}", a + b);
println!("Float subtraction: {}", a - b);
println!("Float multiplication: {}", a * b);
println!("Float division: {}", a / b);
}
Standard Output:
Float addition: 5.5
Float subtraction: 0.5
Float multiplication: 7.5
Float division: 1.2
Main article => Numbers
Characters
In Rust, the character (char
) type is used to represent a single Unicode scalar value. A char can be a letter, number, special character, or even an emoji!
The following shows a few examples of char type variables:
fn main() {
let exclamation_mark: char = '!';
let newline: char = '\n';
let emoji: char = '😀';
println!("Exclamation Mark: {}{}Emoji: {}", exclamation_mark, newline, emoji)
}
Standard Output:
Exclamation Mark: !
Emoji: 😀
Main article => Characters
Strings
A string is a sequence of Unicode characters. There are two kinds of string in Rust: string literals and string objects. Technically a string literal is a slice, while a string object is an actual data type. Let’s take a look at how we can create string literal variables using the let keyword:
fn main() {
let s1 = "Hello";
let s2 = ", World!";
println!("{}{}", s1,s2);
}
Standard Output:
Hello, World!
Main article => Strings
String Literals vs. String Objects
There are some big differences between string literals vs. string objects. One of the most important is that string literals are immutable while string objects can be made mutable using the mut keyword.
The following code shows how we can create a string literal, as well as how to form a string object using the string literal. We then push another string onto the end of the string object to form our output “Hello, World!”.
fn main() {
let s_lit = "Hello"; // String literal
let mut s_obj = String::from(s_lit); //String object
s_obj.push_str(", World!"); // Pushing a value
println!("{}", s_obj); // Printing the string
}
Standard Output:
Hello, World!
Main article => String literals vs. string objects
Booleans
A Boolean (bool) is a data type that can only have two values: true or false. Booleans are used extensively for program logic, and are the basis of conditional expressions.
The following code shows how to initialize Boolean variables:
let a = false;
let b = true;
Check out the main tutorial on Booleans to learn how to work with Booleans using logical operators, if…else expressions, while loops, and more.
Main article => Booleans
Arrays
An array is a sequence of elements of the same type. We can store an array in a variable using square brackets:
fn main() {
let int_array = [1,2,3];
let str_array = ["green", "blue", "red"];
println!("{:?}", int_array);
println!("{:?}", str_array);
}
Standard Output:
[1, 2, 3]
["green", "blue", "red"]
Note that we used the debug trait ‘:?’ inside the curly braces in the println! statement.
The elements inside an array can be accessed by index number, with the first element of the array having an index of 0.
let my_array = [1,2,3];
println!("{}",my_array[0]); // Prints the first element in the array; outputs '1'
Standard Output:
1
Arrays can be made mutable using the mut keyword. This enables us to change the values of array elements, but we can’t change the length or type of any array – even a mutable one.
// Declare and print a mutable array:
let mut my_array = [1,2,3];
println!("{:?}", my_array);
// Change array element values:
my_array = [2,3,4];
println!("{:?}", my_array);
Standard Output:
[1, 2, 3]
[2, 3, 4]
Main article => Arrays
Tuples
Tuples are a compound data type made of a sequence of elements of the same or of different type.
Tuples are similar to arrays. Arrays are also a compound data type, but all of the elements in an array must be of the same data type (i.e. all strings, or booleans). Tuples are useful when a compound type is required whose elements have different types.
Also like arrays, tuples have a fixed length and are immutable by default. Like variables, tuples are declared using the let keyword and can be made mutable using mut.
let my_tuple = ("Bob",42); // Declaring a tuple
We can use the debug trait to print a tuple:
fn main() {
let my_tuple = ("Bill",42);
println!("{:?}", my_tuple);
}
Standard Output:
("Bill", 42)
Elements of a tuple can also be accessed via their index, with the first element having an index of zero. When accessing the tuple elements, we use a dot ‘.’ notation:
fn main() {
let my_tuple = ("Bill",42);
println!("{}", my_tuple.1);
println!("{}", my_tuple.0);
}
Standard Output:
42 Bill
Main article => Tuples
Type Casting
Because Rust enforces strict safety rules, it is common for the compiler to throw an error when there is a type mismatch. For example, we can’t do math using two different numerical types.
In some cases, Rust allows us to cast between types, enabling us to work with data of different types. However, Rust is relatively strict about type casting. The following castings are allowed:
- Integer to integer
- Integer to float
- Float to Integer
- Integer to char
- Char to integer
- Bool to integer
- Integer to string
The as keyword can be used to type cast safely.
The syntax of the as keyword required an operand as well as a data type. For example:
my_var as i32
This will cast the variable my_var as a 32-bit integer type.
Rust has strict rules about what can be type casted, and how type casting works for each. Any time you are type casting, it’s worth running a few lines of code to confirm that it gets casted the way you’re expecting.
For example, casting a char to an int produces the character’s Unicode value.
Main article => Type Casting
Operators
An operator is a symbol that performs an operation on one or more values.
Operators are used to perform different operations, such as mathematical or logical operations. We can divide operators into the following categories:
- Arithmetic Operators
- Logical Operators
- Comparison Operators
- Bitwise Operators
- Assignment Operators
We’ll briefly cover each type below.
Main article => Operators
Arithmetic Operators
Arithmetic operators are used to perform mathematical operations.
There are five basic arithmetic operations in Rust: addition, subtraction, multiplication, division, and remainder.
Each operation has a corresponding operator, as shown in the table below:
Operator | Operation | Syntax | Function |
+ | Addition | a + b | Sum of a and b |
– | Subtraction | a – b | Difference of a and b |
* | Multiplication | a * b | Product of a and b |
/ | Division | a / b | Quotient of a and b |
% | Remainder | a % b | Remainder of division |
fn main() {
let a = 10;
let b = 5;
let sum = a + b;
let diff = a - b;
let prod = a * b;
let quot = a / b;
let rem = a % b;
println!("Sum: {}", sum);
println!("Difference: {}", diff);
println!("Product: {}", prod);
println!("Quotient: {}", quot);
println!("Remainder: {}", rem);
}
Standard Output:
Sum: 15 Difference: 5 Product: 50 Quotient: 2 Remainder: 0
Main article => Arithmetic Operators
Logical Operators
Logical operators are used to perform logic operations. There are three basic logical operations: AND, OR, and NOT.
Logical operators take in Boolean types (i.e. true or false), and produce a Boolean output.
The following table summarizes the three logical operators in Rust:
Operator | Operation | Syntax | Function |
&& | AND | a && b | True if both operands are true |
|| | OR | a || b | True if at least one operand is true |
! | NOT | ! | Switches true to false or false to true |
The following example demonstrates how easy it is to use logical operators in Rust:
fn main() {
let a = true;
let b = false;
println!("And: {}", a && b);
println!("Or: {}", a || b);
println!("Not: {}", !a);
}
Standard Output:
And: false Or: true Not: false
Main article => Logical Operators
Comparison Operators
Comparison operators are used to compare the value of two operands and output a Boolean value.
These include greater than, lesser than, equal to, and others. The following table describes the comparison operators available in Rust, as well as their snyntax:
Operator | Operation | Syntax | Function |
> | Greater than | a > b | True if a is greater than b |
< | Lesser than | a < b | True if a is less than b |
== | Equal to | a == b | True if a is equal to b |
>= | Greater than or equal to | a >= b | True if a is greater than or equal to b |
<= | Lesser than or equal to | a <= b | True if a is less than or equal to b |
!= | Not equal to | a != b | True if a is not equal to b |
Comparison operators are easy to use, and work virtually identically in Rust as in other programming languages.
The following example shows how to use each of the comparison operators:
fn main() {
let a = 5;
let b = 10;
println!("a > b:{}", a > b); // false
println!("a >= b:{}", a >= b); // false
println!("a < b:{}", a < b); // true
println!("a <= b:{}", a <= b); // true
println!("a == b:{}", a == b); // false
println!("a != b:{}", a != b); // true
}
Standard Output:
a > b:false a >= b:false a < b:true a <= b:true a == b:false a != b:true
Main article => Comparison Operators
Bitwise Operators
Bitwise operators are used to interact with the binary representation of operands. Instead of looking at the value of an operand, a bitwise operator looks at its’ bit representation.
They can perform logical operations like AND, OR, NOT, and XOR as well as left and right shifting.
The following table summarizes the characteristics of bitwise operators in the Rust programming language:
Operator | Operation | Syntax | Description |
& | AND | A & B | Bitwise AND |
| | OR | A | B | Bitwise OR |
! | NOT | !A | Bit inversion |
^ | XOR | A ^ B | Bitwise XOR |
<< | Left Shift | A << B | Shift A left by B bits |
>> | Right Shift | A >> B | Shift A right by B bits |
We can use the bitwise AND operation to see how this works. Let’s say we have two integer operands, a and b. The operand a has a value of ‘5’, and b has a value of ‘9’. In terms of bit representation, a is equal to 0101 and b is equal to 1001. (The actual representation depends on the size of the integer, but this simplified example is good enough to show how bitwise operators work).
Bitwise AND looks at each bit position and returns one only if both operands have a 1 in that position:
Operand A = 5 = 0101
Operand B = 9 = 1001
A AND B = A & B = 0001
The other bitwise operators similarly function by operating against an operand at the bit level. Check out the full tutorial if you’d like to learn more!
Main article => Bitwise Operators
Assignment Operators
An assignment operator is used to assign a value to a variable.
We’ve already seen lots of examples of the assignment operator, which we’ve used to bind a value to a variable:
fn main() {
let a = 5; // a is being assigned a value of '5'
println!("Value of 'a': {}", a);
}
Standard Output:
Value of 'a': 5
Beyond this simple example, we can also use compound assignment operators, which perform some action (like addition, subtraction, etc.) and change the value of the variable.
The following table shows the types of assignment operators in Rust:
Operator | Function | Syntax |
= | Assign | a = b |
+= | Add and assign | a += b |
-= | Subtract and assign | a -= b |
*= | Multiply and assign | a *= b |
/= | Divide and assign | a /= b |
%= | Modulus and assign | a %= b |
&= | Bitwise AND and assign | a &= b |
|= | Bitwise OR and assign | a |= b |
^= | Bitwise XOR and assign | a ^= b |
<<= | Bitwise Left Shift and assign | a <<= b |
>>= | Bitwise Right Shift and assign | a >>= b |
Check out the main tutorial on assignment operators if you want to learn more.
Main article => Assignment Operators
Control Flow
Control flow refers to the ability to run code based on a condition or set of conditions. It’s useful whenever we want a block of code to run when a condition is met.
There are two common types of control flow statements: conditional expressions, and loops.
- Conditional expressions
- Loops
- for loops
- while loops
- infinite ‘loop’ loops
We’ll cover each type of control flow statement below.
Main article => Control flow
If…Else Expressions
If expressions allows code to be branched based on a condition or set of conditions. When the if expression is evaluated, the code inside the if statement block will run if the statement is true. If the statement is false, then the code block will be skipped.
In Rust, if expression syntax is similar to that of most other programming languages:
let fav_color = "Green";
if fav_color == "Green" {
println!("Your favorite color is green!")
}
If statements can also contain an else expression. This provides additional functionality; if the evaluated condition is true, then the code in the if block is executed. If it’s false, then the code in the else block gets executed:
if cat_or_dog == "cat" {
// This block executes only if cat_or_dog is equal to "cat"
}
else {
// This block executes if cat_or_dog is not equal to "cat"
}
This is commonly referred to as an ‘if…else’ statement.
When there are more possibilites that need to be addressed, we can use additional ‘else if’ expressions in between the ‘if’ and ‘else’ expressions:
if cat_or_dog == "cat" {
// This block executes only if cat_or_dog is equal to "cat"
}
else if cat_or_dog == "dog" {
// This block executes if cat_or_dog is equal to "dog"
}
else {
// This block executes if cat_or_dog is not equal to "cat" or "dog"
}
However, Rust also has a another great way of handling conditionals with multiple possibilities that need to be handled: match expressions!
Main article => if expressions
Match Expressions
Match is a type of conditional expression that checks to see if a value corresponds with any value on a list of values. Match expressions are great because they simplify code when compared with a long list of if/else statements.
match match_this {
val_1 => {
// This code block executes if match_this equals val_1
},
val_2 => {
// This code block executes if match_this equals val_2
},
val_3 => {
// This code block executes if match_this equals val_3
},
_ => {
// This block executes if the others don't.
}
};
Here’s an example:
let number = 4;
let number_match = match number {
1 => "one",
2 => "two",
3 => "three",
4 => "four",
5 => "five",
_ => "Number not between one and five."
};
println!("You have the number {}!", number_match);
Standard Output:
You have the number four!
There are some limitations to match expressions that we won’t get into here. But you can learn more inside the match tutorial!
Main article => match expression
if let Expressions
If let expressions are a type of conditional expression that allows pattern matching within an if statement. They don’t necessarily add new capability to if statements or match expressions, but it allows us to use a cleaner, simpler syntax in certain cases.
The syntax of an if let expression is as follows:
if let value1 = value2 {}
The code in the block (denoted by the curly braces {}) will execute only if value1 is equal to value2. if let expressions can also be used with else:
fn main() {
let course = "Rust";
if let "Rust" = course {
println!("The patterns match!");
} else {
println!("The patterns don't match!");
}
}
Standard Output:
The patterns match!
Main article => if let expressions
Loops
Loops allow a block of code to be executed repeatedly when/until a specified condition is met.
There are a few types of loops in Rust:
Each type of loop is designed for a different purpose and facilitates control flow in different ways.
Main article => Loops
for loops
A for loop is designed to iterate for a specified number of times, over a designated range.
The range is defined using range notation:
Range notation: a..b
The for loop will loop from the number ‘a’ to the number ‘b’, exclusive of ‘b’.
Here’s how a for loop looks:
for i in 1..5 {
println!("Loop number: {}", i);
}
Standard Output:
Loop number: 1
Loop number: 2
Loop number: 3
Loop number: 4
Main article => for Loops
while Loops
A while loop is used to create a loop that iterates until a specified condition is reached.
A while loop is defined using syntax that includes a condition. The loop executes as long as the condition remains true:
In the following example, the while loop iterates until i reaches 5, incrementing i once with each iteration:
let mut i = 1;
while i < 5 {
println!("i: {}", i);
i += 1;
}
Standard Output:
i: 1
i: 2
i: 3
i: 4
Main article => while Loops
Infinite Loops
The loop keyword can be used to define an infinite loop. The code block defined by loop will execute repeatedly until the break keyword is used.
In order to create a useful loop, break is often located inside a conditional expression such as an if statement.
In the following example, we create an indefinite loop; it will loop repeatedly until the break keyword executes – this occurs when the variable i reaches the value of 3.
let mut i = 0;
loop {
println!("{}",i);
if i == 3 {
break;
}
i += 1;
}
Standard Output:
0
1
2
3
Main article => infinite Loops
More Coming Soon!
This page is currently in development…if you’d like to learn more about Rust, please jump in to the main course on Rust!
Other Resources for Learning Rust
The following are great resources for learning Rust, and are great companions for the content here:
The Rust lang team site: A great resource for all kinds of information about the Rust language.
The Rust Book: Like the Bible, but for Rustaceans and with even more rules! 😉
The Command Line Book: Learn how to build command line applications in Rust.
The WebAssembly Book: Build the internet of the future with Rust and WebAssembly.
The Embedded Book: Apply Rust to embedded systems and make things that do cool stuff.
Rust by Example: A collection of examples that you can run to learn about Rust.