Hello World
-
Create source code file
fn main() { println!("Hello, world!"); }
-
Compile program
$ rustc main.rs
-
Run program
$ ./main
Hello World (Cargo)
-
Create project and enter project directory
$ cargo new hello_world $ cd hello_world
-
Edit source code file
fn main() { println!("Hello, world!"); }
-
Build and run project
$ cargo run
Quickstart: Printing
fn main() {
println!("Hello world!"); // Hello world!
// Interpolation
let x = 5;
println!("This is x: {}", x); // This is x: 5
println!("This is x: {x}"); // This is x: 5
// Debug format
let s = "hello world!";
println!("Sentence: {s}"); // Sentence: hello world!
println!("Sentence: {s:?}"); // Sentence: "hello world!"
// Multiple variables
let y = 6;
println!("{x} + {y} = {sum}", sum = x + y); // 5 + 6 = 11
}
Primitives
Scalar Types
-
Integers -
i8
,i16
,u32
,u64
… -
Floating point numbers -
f32
,f64
-
Characters (Unicode scalar values) -
'a'
,'æ'
-
Booleans -
true
,false
-
Unit type -
()
Compound Types
-
Arrays -
[1, 2, 3, 4, 5]
-
Tuples -
(1, 5.0, false)
Numeric Variables
fn main() {
// Explicit type annotation
let x: i8 = 5;
// Numeric types can use type annotation suffix
let y = 5i8;
let z = 5.0f32;
// Implicit type inference
// Default integer has type `i32`
let v = 5;
// Default float has type `f64`
let w = 5.0;
// Integer literals can have base prefix
let hex_literal = 0xff;
let octal_literal = 0o77;
let binary_literal = 0b1111;
}
Variable Assignment
-
Variables are immutable by default:
fn main() { let immutable = 5; immutable = 6; println!("The value of immutable is {immutable}"); }
-
Mutable variables are declared with the
mut
keyword:fn main() { let mut mutable = 5; mutable = 6; println!("The value of mutable is {mutable}"); }
Scope
-
The scope of a variable is the region of code where the variable name can be used to refer to a value.
fn main() { let x = 5; }
Scope
-
Different values can be referred to by the same name if the variables are in different scopes:
fn main() { let x = 5; { let x = 10; println!("The inner value of x is {x}"); } println!("The outer value of x is {x}"); }
Variable Assignment and Scope
-
Immutable variables in the same scope can be overwritten with shadowing:
fn main() { let immutable = 5; println!("The value of immutable is {immutable}"); let immutable = 6; println!("The value of immutable is {immutable}"); }
Variable Assignment and Scope
-
Shadowing can overwrite the type of a variable:
fn main() { let x = "hello"; println!("The value of x is {x}"); let x = x.len(); println!("The value of x is {x}"); }
Integer Overflow
Unsigned integer overflow wraps.
Signed integer overflow is undefined behavior.
#include <stdio.h>
#include <stdint.h>
int main(void) {
int32_t s = INT32_MAX;
s++;
uint32_t u = UINT32_MAX;
u++;
printf("s = %d\n", s);
printf("u = %d\n", u);
}
In debug
mode, integer overflow panics.
In release
mode, integer overflow wraps.
fn main() {
let mut s = i32::MAX;
s += 1;
let mut u = u32::MAX;
u += 1;
println!("s = {s}");
println!("u = {u}");
}
Integer Overflow In Rust
These methods are available for numeric types to explicitly handle overflow:
fn main() {
let s = i32::MAX;
let wrapping = s.wrapping_add(1);
let overflowing = s.overflowing_add(1);
let checked = s.checked_add(1);
let saturating = s.saturating_add(1);
println!("wrapping: {wrapping:?}");
println!("overflowing: {overflowing:?}");
println!("checked: {checked:?}");
println!("saturating: {saturating:?}");
}
wrapping: -2147483648overflowing: (-2147483648, true)checked: Nonesaturating: 2147483647
Tuples
- Tuples are fixed-length, heterogenous collections of values.
- Tuples are indexed by position using dot access.
-
Tuples can be destructured.
fn main() { let mut tup = (1.0, true, "crab"); println!("Third element = {:?}", tup.2); tup.1 = false; println!("Tuple = {:?}", tup); let (_first, _second, third) = tup; println!("Third element = {:?}", third); }
Unit Type
- A unit is a tuple with 0 elements.
fn main() {
let x = ();
println!("Value: {:?}", x);
}
- Expressions that do not return any value return the unit value.
fn empty_function() {}
fn main() {
let return_value = empty_function();
println!("Value: {:?}", return_value);
}
Arrays
- Arrays are fixed-length, homogenous collections of values.
- Arrays are indexed by position using indexing.
- Array access is bounds-checked at both compile and runtime.
fn main() {
let mut arr = ["Jan", "Fob", "Mar", "Apr", "May"];
println!("Third element = {:?}", arr[2]);
arr[1] = "Feb";
println!("Array = {:?}", arr);
}
Arrays (2)
- The type of an array is specified using the element type and number of elements.
fn main() {
let days: [i32; 3] = [1, 2, 3];
let months: [&str; 5] = ["Jan", "Feb", "Mar", "Apr", "May"];
}
- An array can be initialized with the same value for all elements.
fn main() {
let five_crabs: [&str; 5] = ["crab"; 5];
println!("{:?}", five_crabs);
}
Functions
-
Defining a function requires the
fn
keyword, a function name, a set of parentheses, and a set of curly brackets.The curly brackets denote the function body.
fn empty_function() {}
-
Functions can have parameters, which are listed in the parentheses and must contain the type of the parameter.
fn accepts_numbers(x: u8, y: i32) { // Do something with x and y }
Functions
-
Functions can have a return value. The type of the return value must be listed after the parentheses and prefixed with a right arrow.
fn returns_number() -> i32 { }
Functions (2)
-
Function bodies are a list of statements optionally ending in an expression.
- Expressions are code that produce values, and may cause side effects.
- Ending an expression with a semicolon produces an expression statement, where the result of the expression is ignored.
fn print_number(x: u8) { println!("x = {x}"); }
-
An expression at the end of the function body is the return value of the function.
fn print_number_and_return(x: u8) -> u8 { println!("x = {x}"); x }
if
Expressions
if
expressions allow branching code depending on a boolean condition.
fn main() {
let value = 2;
if value < 3 {
println!("Value is less than 3");
} else if value < 5 {
println!("Value is less than 5");
} else {
println!("Value is greater or equal to 5");
}
}
if
Expressions (2)
Since if
expressions are not statements, they return values that can be assigned to variables.
fn main() {
let value = 2;
let statement: &str = if value < 5 {
"Value is less than 5"
} else {
"Value is greater or equal to 5"
};
println!("Statement: {statement}");
}
if
Expressions (3)
All branches of if
expressions must return a value of the same type:
fn main() {
let statement = "crab";
let points = if statement == "hello world!" {
100
} else {
"this is not a number"
};
println!("Points: {points}");
}
Loops
The loop
keyword is used to make an unconditional loop.
fn main() {
loop {
println!("crab");
}
}
Loops (2)
The break
and continue
keywords allow controlling the flow of the loop.
fn main() {
let mut x = 0;
loop {
println!("{x}");
x += 1;
// `continue` immediately starts the next iteration of the loop
if x == 6 {
continue; // Skip the number 6
}
// `break` immediately ends the loop
if x == 8 {
break; // Stop printing if the number is now 8
}
}
}
Loops (3)
Loops can return values through the break
keyword.
fn main() {
let mut x = 0;
let y = loop {
x += 1;
if x == 5 {
break x * 10;
}
};
println!("y = {y}");
}
Loops (4)
Loops can be labelled with a single quote. Loop labels can be used with break
and continue
.
#![allow(unreachable_code, unused_labels)]
fn main() {
'outer: loop {
'inner: loop {
// break 'inner;
break 'outer;
}
println!("After inner loop");
break;
}
println!("After outer loop");
}
while
Loops
while
loops allow looping based on a boolean condition.
fn main() {
let mut n = 0;
while n != 10 {
println!("Seen {n} sheep jumping over a fence.");
n += 1;
}
println!("Zzzzz...");
}
for
Loops
for
loops allow iterating over a collection.
fn main() {
let months = ["Jan", "Feb", "Mar"];
for element in months {
println!("It is now {element}");
}
}
The while
loop from the previous example may be concisely written using a for
loop and a Range
.
fn main() {
for n in 0..10 {
println!("Seen {n} sheep jumping over a fence.");
}
println!("Zzzzz...");
}
Structs
Structs are defined by specifying the name of the struct, as well as its fields. Each field of the struct has a name and a type.
struct User {
active: bool,
username: String,
sign_in_count: u64,
}
An instance of a struct can be created by supplying each field with a concrete value for that type:
fn main() {
let user = User {
active: true,
username: String::from("crabby"),
sign_in_count: 1,
};
}
Structs (2)
Fields of the struct can be accessed using dot access, including for mutation.
fn main() {
let mut user = User {
active: true,
username: String::from("crabby"),
sign_in_count: 1,
};
user.active = false;
println!("{}", user.username);
}
Structs (3)
When initializing a struct, fields that are not explicitly given can be initialized using values from another struct:
fn main() {
let user_1 = User {
active: true,
username: String::from("crabby"),
sign_in_count: 1,
};
let user_2 = User {
username: String::from("crabby"),
// Note that this line cannot end in a comma
..user_1
};
}
Tuple Structs
Tuple structs are structs that do not have named fields; think of them as named tuples. Tuple struct fields can be accessed using dot notation.
struct Point(i32, i32, i32);
fn main() {
let mut center = Point(0, 0, 0);
center.1 = 1;
println!("{} {} {}", center.0, center.1, center.2);
}
Struct Methods
Struct methods are functions that are attached to an instance of a struct.
They allow access to the struct the method is called on through the self
parameter.
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect_1 = Rectangle { width: 30, height: 50 };
println!("Area of the rectangle: {} square pixels.", rect_1.area());
}
Enumerations
Enumerations, or enums, allow you express that a certain type has one of a few known-before-hand set of values.
enum IpAddrKind {
V4,
V6,
}
fn main() {
let ip_four = IpAddrKind::V4;
let ip_six = IpAddrKind::V6;
}
Enumerations (2)
Enums can store values in the variants. Each variant can store a different type.
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback =IpAddr::V6(String::from("::1"));
}
Enumerations (3)
There are various ways of embedding values in enum variants:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg_a = Message::Quit;
let msg_b = Message::Move { x: 5, y: 6 };
let msg_c = Message::Write(String::from("hello world!"));
let msg_d = Message::ChangeColor(255, 255, 255);
}
Enumerations (4)
You can map enum variants to values with a match
statement:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Enumerations (5)
Match arms (the => ...
) can run multiple lines of code:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Enumerations (6)
Enum variants that store values…
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
Enumerations (7)
…can be extracted in a match
statement:
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {state:?}!");
25
}
}
}