- Published on
Rust has become more popular in recent years. Here is a guide for newcomers. It doesn't cover everything in detail, but is intended to be an overview for those who know other languages but are new to Rust. After reading through this guide you should be familiar with most concepts in Rust and be able to read and understand code.
Table of Contents
- Variables
- const / static
- Types
- Primitive types
- integers
- Floats - f32 and f64
- Strings & characters
- Enums in Rust
- Structs
- Adding methods (functions) to a struct in Rust language
- Casting numbers to chars
- Type inference
- Casting between different types
- Outputting data
- Comments in Rust
- Mutating data
- Shadowing vars
- Matching / flow control
- Match shortform
- References and ownership in Rust
- Creating a reference in Rust
- Mutable references
- Ownership
- Uninitialized variables
- Lists, arrays, collections in Rust
- Arrays
- Create an array of a number of same items
- Accessing data in arrays
- Create mutable arrays
- Vectors
- Creating vectors
- Adding items to a vector in rust
- More helper functions on vectors
- Slices
- Tuples
- Mutating data in functions
Variables
- Declare variables with
let
(and less oftenconst
andstatic
). - You will mostly use
let
fn example() {
let count = 1;
let count2 = count + 1;
println!("counted: {}", count2);
}
- Variables are blocked scoped. (Similar to JS, PHP etc)
const / static
- Less often used.
- You must define the type for these (with
let
you can normally let Rust infer the type automatically). const
is for variables who's values do not change. When these vars are used, the value itself is replaced there.static
can be more like a global variable. It cannot be changed. When these vars are used, they have a fixed memory address which is used.
const UNLUCKY_NUMBER: u64 = 32;
static SOME_COUNTRIES: [&str; 3] = ["USA", "Canada", "Mexico"];
Note: there is a lot of complicated topics about how long variables are in memory. See section further down.
Types
Primitive types
integers
-
Two types of integers in rust. Signed (can be negative or positive) and unsigned.
-
Unsigned:
u8
,u16
,u32
,u64
,u128
andusize
-
Signed:
i8
,i16
,i32
,i128
andusize
-
i8
= 8 bits = 1 byte. -
usize
andisize
depend on the architecture of your computer.isize
/usize
on a 64 bit computer =i64
oru64
You can use underscore to make reading numbers easier. All of the following numbers are equivalent.
fn example() {
let num1: u256 = 1234;
let num2: u256 = 1_234;
let num3: u256 = 1___2_3_4;
}
You can write types at the end of the number to avoid having to use : uXXX
when declaring the var. Both of the following examples are equivalent.
fn example() {
let num1 = 1234u256;
let num2 = 1_234_u256;
}
Floats - f32 and f64
- Floats = numbers with decimal
- Two types,
f32
andf64
. By default Rust will usef64
unless you specify otherwise. - Turn an int into a float by adding a decimal
let an_integer: 10;
let a_float: 10.;
let another_float: 10.0;
How to work with different types of floats
fn example() {
let float1: f64 = 20.;
let float2: f32 = 10.;
let result = float1 + float2 as f64;
}
Strings & characters
Characters
- Characters are a bit easier than full strings, so lets start with these.
- Characters are called
char
in rust - Chars are unicode characters.
a
is a character, but also😊
is a char
Example code setting chars -
fn example() {
let letter_a = 'A';
let unicode = 'Ƣ';
let emoji = '😊';
}
Note: in terms of the size of the data, letter_a
size in memory (1 byte) will be smaller than the emoji
variable (4 bytes).
Strings
- Two types of strings:
String
(pointer, has functions on it)&str
(more simple, faster)- Both types are UTF-8.
&str
- dynamically sized - you can change the size
String
- How to create:
let s1 = String::from("use double quotes here");
let s2 = "Ben".to_string();
let s3 = format!("Hello {}", s2);
Enums in Rust
- enums are a type that has one value (out of a few specific options)
- enums in rust can take arguments, which is something I've not seen in other languages
enum Size {
Small,
Medium,
Large,
}
fn pickASize() -> Size {
return Size.Medium;
}
Structs
- Structs are a way of holding data, and having functions (methods) on those objects.
- You define the shape of the struct, and then optionally can add functions to that struct.
struct Position(f64, f64, f64);
fn example() {
let where_am_i = Struct(12.45, 30.9, 2.);
println!("You are at position x: {}", where_am_i.0);
println!("You are at position y: {}", where_am_i.1);
println!("You are at position z: {}", where_am_i.2);
}
- Structs can have keys:
struct Player {
name: &str,
height: f64,
}
- Structs can contain other structs:
struct Player {
name: &str,
height: f64,
pos: Position, // < struct in a struct
}
Adding methods (functions) to a struct in Rust language
- Use
impl
to add a method to a struct - You can use
&self
to access the data on a struct (likethis
in PHP/JS)
struct Position {
x: f64,
y: f64,
z: f64,
}
impl Position {
fn moveX(&self) {
self.x = 123.45;
}
}
Casting numbers to chars
TODO
Type inference
TODO
Casting between different types
TODO
Outputting data
Easiest way to output is with the println!()
macro.
fn main() {
println!("hello world");
let some_var = 123;
println("hello {}:, some_var);
}
Comments in Rust
You can add comments like in many C based languages
// this is a comment
/* so is this */
let a_variable = true; // here is a comment
Mutating data
- by default vars are immutable.
fn example() {
let count1 = 1; // cannot change this!
let mut count2 = 1; // mutable - can change
count2++;
}
Shadowing vars
In some languages like JS you cannot redeclare a variable in the same scope. In Rust you can. This is typical rust code:
fn example() {
let age = 64;
let age = 70;
println("{}", age); // outputs 70
}
You can even change the type of the variable (age
in this case) every time you redeclare it with let
.
Matching / flow control
- There is no
switch
/case
in Rust like in many other languages. - But there is
match
which is similar.
fn main() {
let what_char = 'a';
match what_char {
'a' => println!("first letter"),
'z' => println!("last letter"),
_ => println!("another letter"),
}
}
- Matches must cover all options (which for char would be very large/impossible to write- but for things such as a enum it is possible) or include a default case
_
.
Match shortform
You can also write them inline and assign a variable to the matched output:
fn main() {
let points = 5;
let group = match points {
0 => 1,
1 => 5,
_ => 10,
};
}
References and ownership in Rust
Creating a reference in Rust
- Use
&
to create a ref.
fn example() {
let name = String::from("Jack");
let name_ref = &name;
println!("Hello {}", name_ref); // outputs Hello Jack
}
Mutable references
Just like normal variables can have the mut
keyword, you can also do this for references.
Here I will show an example with changing a variable via its (mutable) ref. It also shows dereferencing with the *
fn example() {
let mut age = 64;
let age_ref = &mut age;
*age_ref++;
println!("{}", age);
}
Mutable reference rules
- You can have as many immutable references for a var as you want - as long as they're all immutable
- or you can have example 1 mutable reference. (you cannot have an immutable and mutable ref for the same variable).
Ownership
Ownership rules for copy types
- Copy types are simple types, of small size.
- As they are small, and the compiler knows their size they do not need ownership
- copy types =
integers
,floats
,booleans
,char
(characters) - no need to think about ownership for these. They are copied every time they're used. they stay on the stack. quick, easy and small.
- Note:
char
is a copy type, but&str
andString
are not.
Ownership rules for all other types
For all other types, we must think about ownership.
A concept in Rust is ownership. A value is owned only by one owner.
As soon as you use a value (e.g. to pass it to a function), the ownership is removed (and given to something else).
This will not work:
fn say_hi(name: String) {
println!("Hi, {}", name);
}
fn main() {
let who = String::from("Micky"); // note: this is not a `copy type`! its a String
say_hi(who); // outputs "Hi, Micky"
say_hi(who); // fails <<
}
This fails as the first call to say_hi()
passes who
to the say_hi
function, which is now the owner.
Most of the time you will fix this by passing references:
fn say_hi(name: &String) { // note: &String this time!
println("Hi, {}", name);
}
fn main() {
let who = String::from("Donald");
say_hi(&who);
say_hi(&who);
}
this time as the reference (and not value itself) was passed in, the owner is still around after the first call to say_hi
so the second call also works.
You can also return name
in say_hi()
, and use it again like this:
fn say_hi(name: String) { // back to just String
println!("hi {}", name);
return name;
}
fn main() {
let name = String::from("Scrooge");
let name = say_hi(name);
say_hi(name);
}
Uninitialized variables
- These are vars that have no value. These cannot be used in Rust.
- In other languages you can often declare a var (without setting value), and then set the value later on.
Example
fn example() {
let magic_number = if (something > 2) {
400
} else {
0
};
}
Lists, arrays, collections in Rust
- Lots of types exist in Rust to do things that other languages might call arrays, lists or collections.
Arrays
- Arrays in Rust are very fast
- Arrays are of fixed size
- Arrays in Rust are defined with square brackets
[]
. You first define the type, then how many in the array. Example:[&str; 5]
for an array of 5&str
strings. - Arrays contain the same type of data
Create an array of a number of same items
- If you wanted to create a new array in Rust with 5 items in it, all with the same value of
"x"
then you can do this:let five_xs = ["x": 5]
. It is more common to use it to initalise an array with empty data (e.g.let mut some_buffer = [0, 200]
)
Accessing data in arrays
- Use square brackets or dot notation
- They are zero indexed - 0 item on an array if the first item
fn example() {
let top_letters = ["a", "b", "c"];
println!("{}", top_letters.0); // 'a'
let num = 0;
println!("{}", top_letters[num]); // 'a'
}
Create mutable arrays
- You cannot change the size of arrays.
- But use the
mut
keyword to make their contents mutable.
let mut fav_letters = ["a", "b", "c"];
fav_letters[
Vectors
- Vectors are like arrays
- they are slower than arrays
- The size of vectors are dynamic
- Vectors allocate data onm the heap
Creating vectors
Can create vectors with the vec!
macro:
fn example() {
let nums = vec![1, 2, 6]; // Vec<i32>
}
Adding items to a vector in rust
Use the .push()
method.
fn example() {
let mut names = Vec::new();
names.push(String::from("Harry"));
names.push(String::from("Ron"));
}
More helper functions on vectors
fn example() {
let mut names = Vec::new();
println!("{}", names.capacity()); // 0
// after adding items, capacity increases (to 4 then doubles)
names.push("Micky");
println!("{}", names.capacity()); // 4;
println!("{}", names.len()); // 1 - as there is one item in the vector
}
If we already know the expected capacity, we can assign it at initialisation of the vector for a slight performance increase:
fn example() {
let mut names = Vec::with_capacity(4);
// ...
}
Slices
Slices is a reference to an rray.
fn example() {
let array_of_nums = [1, 2, 3, 4, 5, 6];
let slice_ref = &array_of_nums = &array_of_nums[1..3];
for num in slice_ref.iter() {
println!("{}", num);
}
}
Tuples
- Arrays, vectors and slices are always lists of the same type.
- Tuples can be a fixed amount of different types.
- Tuples in rust are defined with
(
and)
fn example() {
let some_result = ("something", true, 200);
// access with dot notation:
println!("the name: {:?}", some_result.0);
println!("the bool: {:?}", some_result.1);
println!("the number: {:?}", some_result.2);
// same as accessing with square brackets:
println!("the name: {:?}", some_result[0]);
println!("the bool: {:?}", some_result[1]);
println!("the number: {:?}", some_result[2]);
}
Mutating data in functions
fn main() {
let name = String::from("Micky");
add_last_name(name);
}
fn add_last_name(mut name: String) {
// `add_last_name` owns `name` now - it wasn't passed by reference
name.push_str(" Mouse");
println!("{}", name);
}