Notes on almost every topic from the book : https://doc.rust-lang.org/book/
I just typed what I felt like should be noted while reading the book simultaneously.
Install
rustup
as a toolchain along withcargo
.
Don't be afraid of compiler errors. They are very helpful and give very good information.
[TOC]
cargo new <project_name> #create new project (binary)
cargo build #compile and check
cargo run #compile(if-not) and run the binary program
make it mutable by
mut
let mut x = 5;
const
for constants because they are not meant to be changed ever.const
for values computed at runtime. like result of function calls.const MAX_POINTS: u32 = 1000_00;
let x = 5;
let x = x+1;// don't use mut!
mut
variables.rust is statically typed
98_222 = 98222 (!you can use _ as separator)
0xfff hex, Oo77 oct, 0b1111_000 binary, b'A' Byte(u8 only).
while using --release
flag, rust won't check for integer overflow . It performs two's compliment wrapping. ex: for u8 , 256 becomes 0,
Wrapping
for explicitly wrappingtrue
or false
char
: 4 bytes , Unicode Scalar Values range fromlet tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup;// pattern matching : x = 500
let first_element = tup.0;
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; //is same as
let a = [3, 3, 3, 3, 3];// 5 elements
a[index]
index out of bounds
error if out of boundsconvention: use snake case.
fn another_function(x: i32, y: i32) {...}
rust is an expression-based language
let x = (let y = 4);
is invalid unlike C because let y = 4 doesn't yield a vlaue ; nothing to bind for xlet y = {
let x = 3;
x+1// look at this. NO SEMICOLON!!
} // this block/scope returns value 4
return
keywordfn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1 // Again no semicolon here.
}
()
empty tuple.//
for each line.///
documentation comment : generates HTML ; used with crates
bool
.arms
let number = if condition { 5 } else { "six" }; // error: both arms should evaluate to same data type
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
break
.let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
for number in (1..4).rev()
. rev()
is to reverseHow rust lays data in memory
ensures memory safety without garbage collector
so cleaning up unused data , minimizing the amount of duplicate data so that we don't run out of space are all problems that ownership addresses.
owner
.owner
at a time.String
types are allocated in heaps.let s = String::from("hello");
creating String
from string literal
string literals
cannot be mutated but Strings
can. Because we know the size of literals and can be hardcoded in the program. Strings are growable
.allocate
and free
drop
function.let s1 = String::from("hello");
let s2 = s1;
s2 = s1
: copies the ptr of s1(stack) to s2 ; pointing to same heap.double free
error.Rust manages this by making s1 invalid , which is what it calls
move
. Only s2 can free the memory.
let s2 = s1.clone()
, deep
ly copies the heap data not just the stack data.let x = 5;
let y = x;
clone
here.we have to keep in mind the
Copy
andDrop
traits. Normally all those basic data types haveCopy
trait.
Copy
traits are borrowed by the function and made invalid.fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() returns the length of a String
(s, length)
}
&String
To make the reference variable mutable, we use
&mut
formut
variable
We cannot borrow mutable reference more than once at a time in a particular scope.
this is to prevent data race :
Just use curly braces { ... } to create a new scope.😉
We also cannot have a mutable reference while we have an immutable one
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
### Dangling references
- *`lifetimes`* help us a lot for such *dangling pointers*: pointers referencing a location in memory that may have been given to someone else.
### The Slice type
- while slicing byte by byte, we are normally working on starting index and ending index. Though these indices may be found, we can't possibly use this since the state of `String` that we are working on might change it's state.
#### String Slices
```rust
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
[starting_index..ending_index]
[0..2]
<=> [..2]
; [3..length_of_string]
<=> [3..]
; [0..length_of_string]
<=> [..]
&str
(string slice) types&str
are immutable.And.. String literals are
&str
.
Don't mess up with immutable/mutable borrowing rule
UTF-8 encoded text might be multibyte. So we need to take care of that too
&string[..]
(whole string) as parameter instead of whole string &string
.let slice = &array[1..3];
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
} // struct definition
fields
let user1 = User {
//key:value
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};// creaing an instance of the User struct
fn build_user(email: String, username: String) -> User {
User {
email, // instead of email:email;
username,
active: true,
sign_in_count: 1,
}
}
struct update syntax
let user2 = User {
email: String::from("[email protected]"),
username: String::from("anotherusername567"),
..user1 // rest of the values are same as that of user1
};
tuple structs
Tuple structs
: structs without named fields
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);
You cannot pass many tuple structs
with same fields as a parameter for a function : Behaves like tuples
again, keep in mind the lifetime
parameter. (Explained later)
// here add this. This is called deriving Debug Trait
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1); //Notice the :? inside
// prints rect1 is Rectangle { width: 30, height: 50 }
}
println!("rect1 is {:?#}", rect1);
even prints with line breaks (pretty print)//after defining struct in above code
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
&self
knows that it is &Rectangle
because it is implemented inside imp Rectangle
context.&self
because we want to borrow the ownership when we have to read.&mut self
to change the instance that we’ve called the method on as part of what the method does.&
, &mut
, or *
so object matches the signature of therect1.can_hold(&rect2));
. here one instance has a method taking another instance as a parameter.imp Rectangle
block:fn can_hold(&self, other: &Rectangle) -> bool {
...
}
so
&self
makes rust clear which instance it is taking about.
useful for creating Constructors
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
let sq = Rectangle::square(3)
.We can use many such
impl { }
blocks for the same struct. (useful for generic types and traits)
enum IpAddressKind {
V4,
V6,
}
let four = IpAddrKind::V4;
: variants of the enum are namespaced under its identifier and we use ::
to resolve the namespace.struct IpAddr {
kind: IpAddrKind,
address: String,
}
fn main() {
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
}
enum IpAddressKind {
V4(u8,u8,u8,u8),
V6(String),
}
let home = IpAddr::V4(192,168,1,1);
let loopback = IpAddr::V6(String::from("::1"));
checkout standard library for IpAddr
impl
from structure using &self
.Null
value in rust. Why?The problem with null values is that if you try to use a null value as a not-null value, you’ll get an error of some kind. Because this null or not-null property is pervasive, it’s extremely easy to make this kind of error.
Option<T>
enum for that.enum Option<T> {
Some(T),
None,
}
Being so useful, it can be used without bringing it to scope. which means we can use Some(T)
and None
without Option::
prefix.
let some_number = Some(5);
we know what value is present
let absent_number: Option<i32> = None;
we don't have a value; essentially a null value.
Remember!
Option<T>
andT
are not the same type
Option<T>
is that we are explicitly deciding that we are going to handle cases that might have null or some value.T
from Option<T>
when the code is handled , what control flow operator can be used ? match operatormatch
control flow operatorenum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
value_in_cents(Coin::Quarter(UsState::Alaska)); //will match with
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => { // will match with this
println!("State quarter from {:?}!", state);
25
}
}
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1), // plus_one(five) below matched this
}
}
let five = Some(5);
let six = plus_one(five);
Option<T>
type and match it.Matches are exhaustive : So there must be an *match
arm
for every possible cases. Rust will throw error otherwise. It's good. Handles everything. But there is a solution for that too. (_
palceholder)
_
placeholder_
pattern and link it =>
with ()
:a unit value which is basically nothing.let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
if let Some(3) = some_u8_value {
println!("three");
}
Some(3)
match but do nothing with any other Some<u8>
value or the None valuematch
. However, we loose the exhaustive checking.