CUSTOMISED
Expert-led training for your team
Dismiss
Rust for Beginners: A Comprehensive 3-Hour Crash Course

7 September 2023

Rust for Beginners: A Comprehensive 3-Hour Crash Course

Rust is a systems programming language that has quickly grown in popularity in recent years. Developed by Mozilla Research, Rust combines speed, safety, and concurrency in one fast systems language.

In this comprehensive, 3-hour crash course article, you'll learn Rust from the ground up through hands-on examples and code samples. We'll cover everything from installing Rust and writing your first Rust program to more advanced topics like ownership, borrowing, and lifetimes.

By the end, you'll have a solid grasp of Rust syntax, how to structure and build Rust projects, and be well on your way to leveraging Rust's key strengths like speed, safety, and fearless concurrency.

This guide is intended as a support tool to our Rust course. get in contact to enroll or discover how we can supply a training solution for you. 

Let's get Rusting!

Why Should Beginners Learn Rust?

As a beginner, you may be wondering — why should I invest time in learning Rust vs other popular languages like JavaScript or Python? Here are some of the key reasons Rust is worth learning:

  • Speed: Rust programs compile down to machine code rather than bytecode, meaning you get performance comparable to C/C++. This makes Rust ideal for performance-critical applications.
  • Safety: Rust's borrow checker ensures memory safety at compile time, eliminating entire classes of bugs like use-after-free errors.
  • Concurrency: Rust makes concurrent programming easy and safe thanks to built-in support for threads, message-passing, and mutexes.
  • Modern language: Rust has expressive syntax and helpful abstractions inspired by languages like Ruby, Haskell, and Erlang.
  • Cross-platform: Compile once and run your Rust code on any platform — Windows, Mac, Linux, and more.
  • Growing ecosystem: Major companies like Microsoft, Amazon, Google, Facebook, Dropbox, and Cloudflare use Rust. The ecosystem is still growing quickly.

So in summary, Rust gives you both high-level ergonomics as well as low-level control, making it a great choice for systems programming. Let's look next at how to setup a Rust programming environment.

Installing Rust

The easiest way to install Rust is via rustup, a command line tool for managing Rust versions and associated tools.

To install rustup and the latest version of Rust, run this in your terminal:


curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

This will install rustup and add the latest stable Rust toolchain to your system PATH.

To verify it worked, you can check the installed version:


rustc --version

Rustup also installs the Rust package manager, cargo, which you will use to create, build, and manage Rust projects. Check it is installed with:


cargo --version
  

With rustup, installing Rust also installs the standard library and compiler. Additionally, you can install debugging tools, IDE integration, and more via rustup components:


rustup component add rust-docs --toolchain nightly
rustup component add rust-analysis --toolchain nightly

Now you are ready to start coding in Rust!

Creating Your First Rust Program

Traditionally, the first program beginners write in a new language prints "Hello, world!" to the terminal. Here is how you would do this in Rust:


fn main() {
  println!("Hello, world!");
}

Let's break down what's happening in this small program:

  • fn main() defines a function called main — this is the entry point of all Rust programs.
  • println! is a Rust macro that prints text to the console.
  • "Hello, world!" is a Rust string literal.

To run the program, save the code in a file hello.rs and compile with rustc:


rustc hello.rs

This will create a binary hello that you can then execute:


./hello

Which prints "Hello, world!" - your first Rust program!

In the next sections, we'll cover more Rust syntax basics including variables, types, functions, if/else expressions, and more.

Rust Syntax Basics

Now that you've written a simple Rust program, let's go over the fundamental building blocks of Rust code.

Variables and Mutability

In Rust, by default variables are immutable. This means once bound to a value, a variable can't be changed.


let x = 5;
x = 6; // error!

To make a variable mutable, use mut:

  
let mut x = 5; // mutable variable
x = 6; // ok!

Constants are also supported, which can never change value once defined:


const API_KEY: &str = "abcd1234"; // constant 

Data Types

Rust is a statically typed language, meaning variables have a defined type set at compile time.

Some common Rust primitive types include:

  • bool - Boolean type with values true and false
  • i32 - 32-bit signed integer
  • u32 - 32-bit unsigned integer
  • f32, f64 - 32 and 64-bit floating point numbers
  • char - Single unicode character
  • str - String slice

For example:


let is_true: bool = true;
let meaning: i32 = 42;
let pi: f64 = 3.141592;
let initial: char = 'C';
let name: &str = "Carol"; 

Tuples can group values of different types:


let tup: (i32, &str, bool) = (50, "Ferris", false);

And arrays collect values of the same type:


let nums: [i32; 5] = [1, 2, 3, 4, 5];

Functions

Functions in Rust are declared with fn:


fn double(x: i32) -> i32 {
  x * 2
}

Let's break this down:

  • fn declares a new function
  • double - the function name
  • (x: i32) - the function parameter x of type i32
  • -> i32 - the return type, also an i32
  • { x * 2 } - the function body, it doubles and returns x

We can call the function like:

  
let result = double(5); // result = 10

Functions are used extensively in Rust to abstract away logic.

Flow Control

Rust has familiar flow control like if/else expressions:


let num = 5;

if num == 5 {
  println!("Number is 5");
} else if num == 6 {
  println!("Number is 6");  
} else {
  println!("Number is not 5 or 6");
}

As well as for loops that iterate over ranges or collection types:

  
for x in 1..11 {
  println!("{}", x); // print 1 to 10
}

This covers some of the basics of Rust syntax! Next we'll look at how Rust handles data allocation.

Understanding Ownership in Rust

A key aspect of Rust is its ownership model for managing memory. Understanding Rust's ownership system is critical to writing safe Rust programs.

Ownership Rules

In Rust, each value has a variable that is its owner. There can only be one owner at a time. When the owner goes out of scope, the value will be dropped.

This ownership model ensures memory safety without needing a garbage collector.

For example:


{
  let s = "hello".to_string(); // s owns the String value

  println!("{}", s); // use the String
} // s goes out of scope and is dropped

Some key ownership rules:

  • When a variable goes out of scope, its value will be dropped.
  • At any given time, a value can only have a single owner.
  • When a variable is assigned to another, ownership moves.

Stack vs Heap

Values in Rust can be stored on the stack or the heap:

  • Stack: For primitive types and references, fixed size, stored inline.
  • Heap: For dynamically sized types like String, variable size, stored as a pointer.

Heap allocation must also be deallocated when the owner goes out of scope. Rust automatically frees heap data once the owner is no longer used, without needing a garbage collector.

Borrowing

Rust allows you to have multiple references to data via borrowing:


let s = String::from("hello"); // s owns the string
  
let len = calculate_length(&s); // borrow s
  
  • &s passes a reference to s without transferring ownership.
  • The reference is only valid while s is in scope.

Borrowing has a few rules:

  • At any time, you can have either one mutable reference or multiple immutable references.
  • References must always be valid — no dangling pointers!

This borrowing system prevents data races and ensures memory safety.

Common Programming Concepts

Now that you understand ownership and borrowing, let's look at how data structures like structs and enums are defined in Rust.

Structs

A struct allows you to create a custom data type:


struct Book {
  title: String,
  author: String,
  pages: i32  
}

let the_rust_book = Book {
  title: "The Rust Programming Language".to_string(),
  author: "Steve Klabnik".to_string(),
  pages: 532,
};

Instances of a struct are created by specifying values for each field.

Methods can be added to structs via impl:

  
impl Book {
  fn summary(&self) {
    println!("{} ({} pages) by {}", self.title, self.pages, self.author);
  }
}

let b = Book { /* fields */ };
b.summary(); // call method

Enums

Enums allow you to define a type with distinct variants:


enum Animal {
  Dog,
  Cat,
  Monkey,   
}

let a = Animal::Dog;

Each variant can store data:


enum Shape {
  Rectangle { width: u32, height: u32},
  Circle(u32),
}

let s = Shape::Circle(7);

Enums are commonly used with match to branch based on the variant:


fn animal_sound(a: Animal) -> &'static str {
   match a {
     Animal::Dog => "bark!",
     Animal::Cat => "meow!",
     Animal::Monkey => "ooooh ooooh ah ah",
   }
}

This covers some of the ways you can organize data in Rust. Next we'll look at error handling.

Handling Errors in Rust

Errors are inevitable in real-world programs. Rust has first-class support for error handling through Result and panic!.

The Result Enum

The Result<T, E> enum is used to indicate an operation that may succeed (Ok) or fail (Err):

  
enum Result<T, E> {
  Ok(T),
  Err(E),
}

For example, parsing a string into an integer could be written as:


fn parse_int(s: &str) -> Result<i32, ParseIntError> {
  match s.parse::<i32>() {
     Ok(i) => Ok(i),
     Err(e) => Err(ParseIntError),
  }
}

Now callers must handle the Result properly:


match parse_int("42") {
  Ok(i) => println!("{}", i),
  Err(e) => println!("Error: {}", e),
}

This prevents errors from being ignored.

Panics

When the program reaches an unrecoverable state, you can call panic!:

  
fn check(i: i32) {
  if i == 0 {
    panic

 

You're absolutely right, my apologies. Here is the rest of the article from where I left off:

  1 / i;
}

A panic will print a failure message, unwind the stack, and quit.

Panic should only be used for exceptional errors, not control flow. Rust's robust error handling prevents many panics.

Advanced Concepts

We've covered the basics so far. Now let's discuss some of Rust's advanced concepts that really set it apart: generics, traits, and concurrency.

Generics

Generics allow code to be abstracted over different types:


struct Point<T> {
  x: T,
  y: T,
}

let int_point = Point{ x: 10, y: 20 };
let float_point = Point{ x: 1.2, y: 4.5 };

The generic type T is substituted with concrete types at compile time.

Traits

Traits are similar to interfaces in other languages. They define shared behavior that types can implement:


trait Log {
  fn log(&self); 
}

struct Person { name: String }
impl Log for Person {
  fn log(&self) {
    println!("My name is {}", self.name);
  }
}

let person = Person{ name: "John".to_string() };
person.log();

Traits allow code reuse across many types.

Concurrency

Rust provides multiple concurrency primitives:

Threads

use std::thread;

thread::spawn(|| {
  // code in this thread executes concurrently
});

Channels
  
use std::sync::mpsc;

let (tx, rx) = mpsc::channel();
tx.send("hi").unwrap();
let msg = rx.recv();

Channels allow threads to communicate without sharing memory.

Mutexes

use std::sync::Mutex;

let m = Mutex::new(0); 
{
  let mut val = m.lock().unwrap();
  *val += 1;
}

Mutexes allow concurrent access to shared data. Rust prevents data races at compile time.

This gives a brief intro to some of Rust's advanced features for generics, traits, and concurrency. Let's now look at building real Rust projects.

Setting Up a Rust Project

Cargo is Rust's built-in package manager and build tool. It lets you manage dependencies and build/test Rust projects easily.

Creating a New Project

To create a new Rust project, use cargo new:


cargo new my-project
     Created binary (application) `my-project` package

This generates a simple project with the following files:


my-project
|- Cargo.toml
|- src
  |- main.rs

  • Cargo.toml contains project metadata and dependencies.
  • src/main.rs contains the project code.

Adding Dependencies

You can include external crates from crates.io by adding them under [dependencies] in Cargo.toml:


[dependencies]
ferris-says = "0.2" 

Then import and use the crate in main.rs:


extern crate ferris_says;

use ferris_says::say;

fn main() {
  say("Hello fellow Rustaceans!");
}

Cargo handles downloading and building the dependencies automatically.

Building and Running

Use cargo build to compile your project:


cargo build
   Compiling my-project v0.1.0

Then run the compiled executable with cargo run:

   
cargo run
   Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
    Running `target/debug/my-project`
Hello fellow Rustaceans! 

That covers the basics of creating, building, and running a Rust project locally!

What's Next? Learning More Rust

I hope this introduction has shown you how Rust provides both high-level ergonomics as well as low-level control. Your next step on the journey is to build something hands-on!

Here are some ideas for where to go from here:

The Rust language has a great ecosystem of tools, libraries, and training resources. Welcome to the community! Rust's combination of performance, safety, and productivity make it a joy to use.

Rust Training is just one of the courses you can enroll in with JBI Training you might also be interested in 

  • SharePoint 2016 Users / Site Owners: This course provides SharePoint users and site owners with the knowledge and skills to effectively use SharePoint sites and portals. Topics include site navigation, content management, permissions, workflows, and customisation.
  • Blockchain: This course offers an introduction to blockchain technology and its applications. Students will learn about distributed ledgers, smart contracts, cryptocurrencies, and building blockchain solutions. Real-world blockchain use cases are examined. 
  • PCI DSS Compliance OWASP 2017: In this course, students learn about PCI DSS standards for securing payment card data. Topics covered include data protection, vulnerability management, access controls, encryption, and testing procedures. The course is updated with the latest 2017 OWASP guidelines.
  • Secure coding in ASP.NET: This course teaches developers how to build secure ASP.NET web applications. Topics include authentication, authorization, input validation, output encoding, encryption, error handling, logging, and testing. Students will learn secure coding principles and best practices.
  • Secure coding in PHP: This course provides PHP developers with secure coding strategies and techniques. Students will learn about vulnerabilities, threats, and attacks, along with methods for sanitizing inputs, preventing SQL injection, using encryption, and writing secure code. Best practices for security are covered.
About the author: Daniel West
Tech Blogger & Researcher for JBI Training

CONTACT
+44 (0)20 8446 7555

[email protected]

SHARE

 

Copyright © 2024 JBI Training. All Rights Reserved.
JB International Training Ltd  -  Company Registration Number: 08458005
Registered Address: Wohl Enterprise Hub, 2B Redbourne Avenue, London, N3 2BS

Modern Slavery Statement & Corporate Policies | Terms & Conditions | Contact Us

POPULAR

Rust training course                                                                          React training course

Threat modelling training course   Python for data analysts training course

Power BI training course                                   Machine Learning training course

Spring Boot Microservices training course              Terraform training course

Kubernetes training course                                                            C++ training course

Power Automate training course                               Clean Code training course