Rust is a system programming language. Used in building web browsers, web servers, databases, firmware, tools and utilities, gaming and operating systems. No garbage collection. Memory safety. Use after free, Dangling pointers, Null pointer exceptions, Iterator invalidation, data races. Zero cost Abstractions. Integrated build tooling Package Manager Open-source package repository Default testing framework Autogenerated documentation. Community- Rust users forum, Internals forum, discord, RustConf, This week in Rust newsletter, rust-lang on twitter and reddit.
update - rustup update
Hello, world main.rs
fn main() {
println!("Hello, world!");
}
Compile rustc main.rs
Run ./main
.
cargo hello world
cargo new hello-world
cargo build
cargo run
Variables
Variables are bound to values using the keyword argument let
.
Immutable by default.This creates predictability in our code.
Can be mutable by adding mut
keyword.
Constants are declared with const
keyword. Names of these constants are capitalized with underscore. can only be set to expressions.
Shadow a variable. Declare a new variable with the same name as the previous, creating a new binding.The new variable will shadow the old variable.
fn main() {
let mut x = 1;
x = 3;
println!("The value of x is : {}", x);
let y = true;
let y = false;
println!("The value of y is : {}", y);
const STRING: &str = "hello";
println!("The value of constant string is : {}", STRING);
}
Data Types
Scaler types
-
Integers
- Integers are whole numbers.
- They’re either signed or unsigned(+ve or -ve).
- compiler must know the data types for each variable in your code.
- The data type can be inferred by the compiler.It defaults to i32.
- isize, usize(8 bit, i8, u8)
-
Floating point
- Numbers with decimal points.
- types: f32, f64
- default: f64.
-
Booleans
- have a value of either ‘true’ or ‘false’.
- specified using keyword
bool
. - 1 byte in size.
- Used most in conditional and control flow statements.
-
Characters
- Represents letters.
- specified using keyword
char
. - Use single quotes.
fn main() {
let a = 5; //i32
let b: u8 = 1;// u8
let x = 2.0; //f64
let y: f32 = 3.0; //f32
let sum = a + b;
let difference = x - 1.0;
let product = 4 * a;
let quotient = 9.0 / y;
let remainder = a % b;
let t = true;
let f = false;
let d = 'D';
}
Compound types
-
Arrays
- Continuous group of items.
- Fixed length.
- Length known at compile time.
- heterogeneous(contain items of the same data type)
- Access items by index.
- compiler warns when index is known to be out of bounds.
- Panics at runtime when index is out of bounds.
-
Tuples
- Continuous group of items.
- fixed length.
- Length known at compile time.
- Homogeneous(contain items of the different data types)
- Empty tuple are called
unit
. - Access items by index using dot notation.
- Impossible to access items out of bounds.
fn main() {
let array = [1u32, 3, 4];
let first_element = array[0];
let len = "some text".len();
let tuple = (1u32, 2,"do", true);
let first_element = tuple.0;
}
-
Functions
- Start with
fn
. - The function name.
- An optional list of arguments. Stating arguments type is required.
- An optional return type. A return type is required if a value is returned. If not return type is unit.
- The function body.
- Start with
fn main() {
println!("Hello, world!");
last_char(String::from("Hello"));
}
fn last_char(string: String) -> char {
if string.is_empty() {
return 'O';
}
string.chars().next_back().unwrap()
}
-
Structs
- A type composed of other types
- Can contain different types.
-
Three flavours of structs
- Classic - Most commonly used, each field has a name and a type.
- Tuple - Similar to classic structs, have no names.
- Unit - have no fields, similar to () unit type.
- Use keyword
struct
followed by the name of the struct. - Name describe the object appropriately.
- Create an instance of the struct by supplying
key: value
pairs. - More instances can be created.
- access properties with the dot notation.
struct Person {
name: String,
age: u8,
likes_oranges: bool
}
struct Point2D(u32, u32);
fn main() {
let person = Person{
name: String::from("Davis"),
likes_oranges: false,
age: 20,
};
println!("Person name is: {}", person.name);
let origin = Point2D(100, 200);
println!(" Point contains {:?} and {:?}", origin.0, origin.1);
//destructuring
let Point2D(x, y) = origin;
println!(" Point contains {:?} and {:?}", x, y)
}
Structs from rust book
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
//Associative functions
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let mut user = User{
email: String::from("john@example.com"),
username: String::from("john"),
active: true,
sign_in_count:1
};
let name = user.username;
user.username = String::from("john_doe");
let user2 = build_user(String::from("janedoe@email.com"), String::from("jane"));
let user3 = User {
email: String::from("Jack@example.com"),
username: String::from("jack"),
..user2
};
//tuple structs
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let rect = Rectangle {
width: 30,
height: 50
};
println!("rect: {:#?}", rect);
println!("The area of the rectangle is: {}", area(&rect));
//using implementation method
println!("The area of the rectangle is: {}", rect.area());
let rect2 = Rectangle {
width: 20,
height: 40
};
let rect3 = Rectangle {
width:40,
height: 50,
};
println!("rect can hold rect1: {}", rect.can_hold(&rect2));
println!("rect can hold rect1: {}", rect.can_hold(&rect3));
let rect4 = Rectangle::square(25);
println!("{:#?}", rect4);
}
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count:1,
}
}
fn area(dimensions: &Rectangle) -> u32 {
dimensions.width * dimensions.height
}
-
Enums
- List all variations of some data.
- Common across programming languages.
- Referred to as algebraic data types.
- use the keyword
enum
followed by the name. - List all variations.
- The enum is now a custom data type that can be used in code.
- Can include any kind of data.
- can have a variety of types.
-
Similar to structs but with more flexibility and advantages.
- Describe what kind of data will be stored.
- Each variant can have a different type.
- All variants stored under the custom enum type.
enum WebEvent {
PageLoad,
PageUnload,
KeyPress(char),
Paste(String),
Click{x:i64, y:i64},
}
enum Option <T> {
Some(T),
None,
}
fn main() {
let quit = WebEvent::KeyPress('q');
let something = Some(1);
}
Enums from rust book
enum IPAddrKind {
v4,
v4u(u8, u8, u8, u8),
v6(String),
}
struct IPAddr {
kind: IPAddrKind,
address: String,
}
enum Message {
Quit,
Move { x: i32, y: i32},
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn some_function() {
println!("enum implements Message");
}
}
fn main() {
let four = IPAddrKind::v4;
let six = IPAddrKind::v6;
let localhost = IPAddr {
kind: IPAddrKind::v4,
address: String::from("127.0.0.1"),
};
let localhost2 = IPAddrKind::v4u(127, 0, 0, 1);
// enum Option<T> {
// Some(T),
// None,
// }
let some_number = Some(1);
let some_string = Some("A string");
let absent_number: Option<i32> = None;
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y.unwrap_or(0);
println!("{}", sum);
value_in_cents(Coin::Quarter(UsState::Alaska));
let ten = Some(10);
let eleven = plus_one(ten);
let none = plus_one(None);
let some_value = Some(3);
match some_value {
Some(3) => println!("Three"),
_ => (),
}
// better approach
if let Some(3) = some_value {
println!("Three");
}
}
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
Arizona,
Arkansas,
California,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
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
}
}
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
// _ => None,
}
}
Control flow
if … else, else if expressions
Provide a condition , and then execute a block of code if the condition evaluates to true. An else expression can be added optionally. If no else is provided, the program will skip the if block if the condition evaluates to false. If you have more than two conditions to check, if and else can be combined in an else if expression.
In the case all if and else if conditions evaluates to false, then the else block is executed.
match
Similar to switch in other programming languages. A scrutiny expression is provided to compare to the patterns. Arms are evaluated and compared with the scrutiny expression.
The scrutiny expression is x. Each arm has a pattern and some code. The ”=>” operator separates the pattern and the code to run. The first arm with the matching pattern is executed.
fn main() {
if 1 == 2 {
println!("match is broken");
} else {
println!("Everything is fine");
}
let formal = true;
let greeting = if formal {
println!("Good evening");
} else {
println!("Hey, friend!");
};
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 {
println!("Number is not divisible by 3 or 4");
}
let boolean = true;
let binary = match boolean {
false => 0,
true => 1,
};
}
Loop
Used to execute over a block of code forever. Or until it is stopped, or the program quits.
Instead of having this code run infinitely, the break
keyword can be used.
while loop
Conditional loops Run until condition is met or become false.
for loop
Iterate through elements in a collection. Each pass of the loop extracts a values.
fn main() {
let mut i = 1;
let something = loop {
i *= 2;
if i > 100 {
break i;
}
};
assert_eq!(something, 128);
let mut counter = 0;
while counter < 10 {
println!("Hello");
counter += 1;
}
for item in 0..5{
println!("{}", item*2);
}
}
Error handling
Simplest method is using the panic!()
macro.
What happens when panic is encountered is:
- Failure message is printed.
- Program unwinds and cleans up the stack. A panic should be used only when a program comes to unrecoverable state. Rust emits a panic during code execution.
use std::fs::File;
fn main() {
// let v = vec![0, 1, 2, 3 ];
// println!("{}", v[6]);
// panic!("I Panicked!");
let fruits = vec!["banana", "apple", "orange", "coconuts"];
let first = fruits.get(0);
println!("{:?}", first);
let third = fruits.get(2);
println!("{:?}", third);
let non_existent = fruits.get(399);
println!("{:?}", non_existent);
let f = File::open("hello.txt").unwrap(); //or use .expect("Failed to open hello.txt"); or
// let f = match f {
// Ok(file) => file,
// Err(error) => panic!("Can't open file {:?}", error)
// };
}
Ownership
About how your program manages memory. Rust stores data in 2 different structured parts of memory.
- Stack - Last In, First Out(LIFO).Stores data with a known fixed size at compile time.
- Heap
A vector is mutable hence it’s size can change when the program is running. Vector object stored on stack with memory pointer to heap. Value of vector is stored in the heap.
Rules of ownership
- Each value in Rust has a variable that is called its owner.
- There can only be one owner at a time. Rust invalidates the owner.
- When the owner goes out of scope, the value will be dropped.
Variable scope - Range within a program for which that variable and the value are valid.
When we pass a variable as a parameter into a function, we transfer the ownership to that function.
throws an error
fn main() {
let say = String::from("Cat");
print_out(say);
println!("Again! {}, ", say);
}
fn print_out(to_print: String) {
println!("{}", to_print);
}
Works
fn main() {
let say = String::from("Cat");
print_out(say.clone());
println!("Again! {}, ", say);
}
fn print_out(to_print: String) {
println!("{}", to_print);
}
Ownership prevents memory safety issues
- Dangling pointer
- Double-free - trying to free memory that has already been freed.
- Memory leaks - not freeing memory that should have been freed.
Ownership from rust book
You can have multiple immutable reference but not multiple immutable references.
fn main() {
// Ownership rules
//1. Each variable in rust has a value called it's owner
//2. There can only be one owner at a time
//3. When the owner goes out of scope, the value will be dropped.
//Stacks and heaps
fn a() {
let x = "hello";
let y = 32;
b()
}
fn b() {
let x = String::from("world");
}
{// s is not valid here, it's not yet declared
let s= "Hello, world!"; //s is valid from this point on
let s2= String::from("Hello, world!"); //s is valid from this point on
// you can do things with s
}// this scope is now over, and is no longer valid
let x = 5;
let y = x; //copy for integers, characters, booleans
let string1 = String::from("Hello");
let string2 = string1; //A move i s performed not shallow copy
let string3 = string2.clone(); //A clone
let string4 = String::from("Takes Ownership");
takes_ownership(string4);
//println!("{}", string4); // returns an error, ownership lost to the function
let my_int = 5;
makes_copy(my_int);
println!("{}", my_int); // prints the integer my_int
let string5 = gives_ownership();
println!("{}", string5); // prints the string
let string6 = takes_and_gives_back(string5); // takes ownership and gives back
println!("{}", string6);//prints the string
let string_7 = String::from("Does'nt take ownership");
let (string_8, len) = calculate_length(string_7);
println!("The length of {} is {}", string_8, len);
//or
let string_7_2 = String::from("Does'nt take ownership");
let len= calculate_length_2(&string_7_2);
println!("The length of {} is {}", string_7_2, len);
let mut string_9 = String::from("Hello");
change_reference(&mut string_9);
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
}
fn makes_copy(some_integer:i32) {
println!("{}", some_integer);
}
fn gives_ownership() -> String {
let some_string = String::from("Hello, world!");
some_string
}
fn takes_and_gives_back( a_string: String) -> String {
a_string
}
fn calculate_length(s:String) -> ( String, usize) {
let length = s.len();
(s, length)
}
fn calculate_length_2(s: &String) -> usize {
let length = s.len();
length
}
fn change_reference(some_string: &mut String) {
some_string.push_str(", world");
}
Borrowing
Creates a reference to a value.
Rules for borrowing
-
At any given time, you can have either:
- One mutable reference, or
- Any number of immutable references.
-
References to must always be be valid
- A value is no longer valid after it is dropped.
- Any reference to that value also become invalid when it is dropped.
Works
fn main() {
let say = String::from("Cat");
let say2 = &say;
println!("{}", say);
println!("{}", say2);
}
Errors
fn main() {
let say = String::from("Cat");
let say2 = &say;
println!("{}", say);
drop(say);
println!("{}", say2);
}
fn main() {
let say = String::from("Cat");
print_out(&say);
println!("Again! {}, ", say);
}
fn print_out(to_print: &String) {
println!("{}", to_print);
}
fn main() {
let mut my_vec = vec![1,2,3];
println!("{:?}", my_vec);
add_to_vec(&mut my_vec);
println!("{:?}", my_vec);
}
fn add_to_vec(a_vec: &mut Vec<i32>) {
a_vec.push(4);
}
Look into “string slices” &str.
Strings
String types in rust include: String and &str, CString and &CStr, OsString and &OsStr.
The String type
- An owned string.
- Owns string data.
- Data freed when dropped.
fn main() {
string_len(String::from("Hello, world!"));
}
fn string_len(s: String) -> usize {
s.len()
}
// s is dropped here
String memory consist of 3 parts
- Length
- Capacity
- Data pointer
The &str type
- A borrowed string slice.
- Does not own string data.
- Data not freed when dropped.
- View/window into string data.
&str memory contains 2 parts:
- Length
- Data pointer
fn main() {
string_len("Hello, world!");
}
fn string_len(s: &str) -> usize {
s.len()
}
** String literals** are embedded into the binary, and have a type &str.
In rust Characters:
- Strings are not arrays of characters.
- Not every byte is a full character.
- Access data using explicit methods.
fn main() {
let text = "Hello\nWorld\n!";
let upper = text.to_uppercase();
let stripped = upper.strip_prefix("HELLO\n").unwrap();
println!("{}", first_line(stripped));
}
// pub fn first_line(string: String) -> String {
// string.lines().next().unwrap().to_owned()
// }
pub fn first_line(string: &str) -> &str {
string.lines().next().unwrap()
}
Collections
-
Vec
- Access elements by index.(continuos collection of items) -
Consist of 3 parts( Length, capacity, data pointer) Slices - continuos chunks of memory, refer to data owned by another value. Written as &[T]. Has 2 parts , length and data pointer.
let vec = vec![1u8, 2, 3]; let s = &v[0..3];
-
- HashMap<K, V> - Associate keys with values.
- HashSet
- Unique items and no access by index. - VecDeque
- A queue. - LinkedList
- Doubly linked list.
fn main() {
let mut students = vec![Student{
name: "John".to_string(),
}];
students.push(Student{
name: "Jane".to_string(),
});
assert!(
&students[0] == &Student{
name: "John".to_string()
}
);
assert!(
students.get(0) == Some(&Student{ name: "John".to_string()})
);
assert!(
students.get(1000) == None
);
for student in students.iter() {
println!("Student name: {}", student.name);
}
use std::collections::HashMap;
let mut enrollment = HashMap::new();
enrollment.insert("biology".to_string(), students);
let bio_students = enrollment.get("biology");
let students = enrollment.remove("biology");
}
#[derive(PartialEq, Eq,)]
struct Student {
name: String,
}
Traits
In rust data(enums/structs) and behavior(traits) are stored separately. Can define default behavior for a method
pub struct Person {
name: String
}
pub struct Cat {
name: String
}
pub struct Rabbit {
name: String
}
pub trait Eat {
fn eat_dinner(&self) {
println!("I eat from a dish");
}
}
impl Eat for Person {
fn eat_dinner(&self) {
println!("I eat from a plate");
}
}
impl Eat for Cat {
fn eat_dinner(&self) {
println!("I eat from a cat bowl");
}
}
impl Eat for Rabbit {
}
fn main() {
let person = Person {
name: String::from("Davis")
};
person.eat_dinner();
let cat = Cat {
name: String::from("Zane")
};
cat.eat_dinner();
let rabbit = Rabbit {
name: String::from("Rabbit")
};
rabbit.eat_dinner();
}
struct Film {
title: String,
director: String,
studio: String,
}
struct Book {
title: String,
author: String,
publisher: String,
}
trait Catalog {
fn describe(&self){
println!("We need more info about this type of media");
}
}
impl Catalog for Film {
fn describe(&self) {
println!("{} was directed by {} through {} studios", self.title, self.director, self.studio);
}
}
impl Catalog for Book {
fn describe(&self) {
println!("{} was written by {} and published by {} .", self.title, self.author, self.publisher);
}
}
struct Album {
title: String,
artist: String,
label: String
}
impl Catalog for Album {
}
fn main() {
let capt_marvel = Film {
title: String::from("Captain Marvel"),
director: String::from("Ann Boden and Ryan Fleck"),
studio: String::from("Marvel")
};
capt_marvel.describe();
let elantris = Book {
title: String::from("Elantris"),
author: String::from("Brandon Sanderson"),
publisher: String::from("Tor Books")
};
elantris.describe();
let let_it_be = Album {
title: String::from("Let it be"),
artist: String::from("Beatles"),
label: String::from("Apple")
};
let_it_be.describe();
}
Build a guessing game
Install a dependencies by adding it to cargo.toml
and run cargo build
Build a rust Guessing game
Add rand to cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.5.5"
colored = "2.0.0"
use std::io;
use rand::Rng;
use std::cmp::Ordering;
use colored::*;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is: {}", secret_number);
loop {
println!("Please input your guess");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("{}","Too Small!".red()),
Ordering::Greater => println!("{}","Too Big!".red()),
Ordering::Equal => {
println!("{}","You win!".green());
break;
}
}
}
}