15 KiB
Data Types
Constants
constants can be delared anywhere, convension to use all caps in const need const keyword, not always eval at compile time variables can only be assigned once (needs mut to assign more than once (need to be same type))
const SECONDS_PER_HOUR: i32 = 60 * 60;
Variables
variables are immuatable by default variables can be inferred but sometimes needs explicit typing
let foo = 5;
need to add mut keyword to enable rewriting, generally avoid unless actually used
let mut bar = 6;
SHADOWING
Cannot have mutable shadows
allows for reuse of namespace instead of spaces_str and spaces_num
let spaces = " _ _ ";
let spaces = spaces.len();
// will output 5 instead of " _ _ " beacuse that is how long it is
// the shadow of spaces (first) wont be printed until the overshadow of spaces goes out of scope
println!("{spaces}"); // output: 5
not allowed shadow
let mut spaces = " _ _ ";
spaces = spaces.len();
cannot change type of variable once declared
Primitive Data Types
Scalars
Integers
u is for usigned integers i is for signed integers
number indicated how many bits it takes in memory
let z: i8; // takes up 8 bits, can store values from -128 to 127
let c: i16; // takes up 16 bits
let d: i32; // takes up 32 bits (default for integers)
let e: i64; // takes up 64 bits
let f: i128; // takes up 128 bits
let g: isize; // takes up x bits, depends on the system's architecture/cpu
let h: u8; // takes up 8 bits, unsigned version (only positive)
// can store values from 0 to 255
Integer Overflow
will reset to the lowest value ie i8 129 -> -126
let example_over_flow: i8 = 129;
behavor only in production mode dev mode will cause a panic and error out/tell you
Floats
better to use double point due to modern cpus where there is not much difference in speed
Single Point Float
takes up 32 bits
let a: f32 = 4.0;
Double Point Float
takes up 64 bits
let b: f64 = 2.01;
Integers Represented Differently
can represent values in hex, oct, bin or dec can hover with rust-analyzer extension to see value in dec
Dec with Reading Aid
value stored 1000 _ used to make easier to read
let i = 1_000;
Hexidecimal
value stored 255
let j = 0xff;
Octal
value stored 63
let k = 0o77;
Binary
value stored 13
let l = 0b1101;
Bytes
u8 only value stored 0x41 or 65
let m = b'A';
Numeric Operators / Basic Math
Numbers for reference
let x: i16 = 8;
let y: i16 = 5;
Addition
let sum = x + y; // result: 13
Subtraction
let difference = x - y; //result: 3
Multiplication
let product: i16;
product = x * y;
Division
let quotent = 45.1 / 54.2;
let truncated = x / y; // results in 1 (always rounds down)
Remainder
let remainder = x % y;
Booleans
must be explicity typed to true or false 0 or 1 not allowed even with let var: bool
let n = false;
let o = true;
Char
must use single quotes and not "" otherwise will be inferred as string literal is stored as Unicode Scalar Value allowing for emoji, japanse char and other languages not supported by ASCII takes 4 bytes in size or 32 bits
let p = 'a';
Compound Types
multiple values into one type
Tuple
A general way of grouping multiple a number of values into one compound type types do not need to be the same in every position
let tup: (i32, f64, u8) = (500, 6.4, 1);
The variable tup has values written to it at initialization but it is not requried, order does not matter similar to a struct in c
Vaules must be destructed out of a tuple to be accessed inidivually, can use a pattern matching to the tuple
let (q, r, s) = tup;
This is called destructing becasue it breaks it into 3 parts
Can also be accessed with a .
INDEX STARTS AT 0
let t = tup.0; // t = 500
let u = tup.1; // u = 6.4
let v = tup.2; // v = 1
A Unit
This is a special value where a tuple has no values
let w: () = ();
This represents an empty type or an empty return type
Expressions will implicitly return a unit if they dont return anything else
Array
A collection of multiple values Must have every value be the same type, cannot mix and match Arrays must be a fixed length at initialization useful when you want a set number of values or is static
Values are in [] and seperated by ,
let xa = [1, 2, 3, 4, 5, 6];
Array located in stack same with above types
If you need your array/list to grow or shrink use a vector If unsure weather to use an Array or Vector pick a vector
Times where using an array is better
let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
Accessing items in an array
let ya = xa[0]; //value is 1
Initializing an Array
let za: [i32; 5]; // allows for 5 32 bit signed integers inside
let aa = [i8; 6]; // allow for 6 8 bit signed integers inside
Invalid Array Elements
use std::io;
fn main() {
let a = [1, 2, 3, 4, 5];
// Input of a number
println!("Please enter an array index.");
let mut index = String::new();
io::stdin()
.read_line(&mut index)
.expect("Failed to read line");
// change into a integer
let index: usize = index
.trim()
.parse()
.expect("Index entered was not a number");
// access elemetn in array
let element = a[index];
println!("The value of the element at index {index} is: {element}");
}
this program would compile with not problems for example inputting 7 into the program this would cause a runtime error the program would output an error because it didnt get to the final line println! before exiting it casue the program to exit before attempting to access the invalid space this is a form of safe memory management that rust name
Complex Data Type
String Literal
This is a string literal it is hardcoded into a program Always immutable Fast and efficient, stored on the stack, property of being immuatable not of any real value
let s: &str = "hello";
String
This is a string that is stored on the heap, this can store data unkown (size, char, etc) to you at compile time Can be mutable, but must request space on the heap then return that memory to the heap, will be returned as soon as it is no longer valid (it calls the drop method from String) not as fast and efficient Example of a string being created form a string literal
let ab:String = String::from("hello");
String concatinization example
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() appends a literal to a String
println!("{s}"); // This will print `hello, world!`
Structures
Custom data type that packages up multiple data types into a meaningful manner and call the collection something More similar to an object, can define related methods to them
Similar to tuples but have to name and define everything inside a struct, like a dictionary but with set names and order Dont need to know order just know key
Defining
Need struct
keywork then name of struct, which should describe the significance of the gropued data
All values are seperated by commas these are called fields
general definition of the type created
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
Initiating
To use give a owning var name thne concretely define what each value is define the key: vaule pairs dont need to initate in the same order they were defined in
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
to access values from the struct the dot
notation is used
note the WHOLE struct must be mutable, rust does not allow for partial mutability
// user1 email field now is equal to the string example@mail.com
user1.email = String::from("example@mail.com");
Can build a struct with implicit values input by default
fn build_user (email: String, username: String) {
User {
active: true,
email: email,
username: username,
sign_in_count: 1,
}
}
Init Field Shorthand
This is useful when the param and the struct definition share the same name. This reduces the amount of time spent on repeating key:value pairs
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
Only works beacuse param share same name as field key
this is equivalent to username:username or email:email
Creating Instances from Other Instances with Struct Update Syntax
Often useful to do so, only need to change 1 value
Slow method
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@emial.com"),
sign_in_count: user.sign_in_count,
};
Using Update syntax ..
this can be done a LOT Quicker
let user2 = User {
email: String::from("another@email.com"),
..user1
}
// user1 no longer completely valid
// can still use user1.email, .active and .sign_in_count
This specifies htat the fields not explicity set should be the same as the given instance This uses the = assignment operator and therefore a ownership move occurs with the ../update syntax
user1 would still be valid if both of the String types in were given new values
Tuple Structs
This is also allowed but not key:value pairs This still holds values in the field
struct RGBColour (i32, i32, i32);
struct Point (i32, i32, i32);
let black = RGBColour(0, 0, 0);
let origin = Point(0, 0, 0);
Unit Like Struct
This is similar to a unit () This holds no data in itself
Useful for when you need to implement a trait on some type but dont want to store data in the type itself
Delcaration
struct unit_like_type;
let using_unit_like_struct = unit_like_type; // instance of unit_like_type
No need for () in the delcaration
Structure Ownership
Want each instance of a struct to own the values inside so that the values inside are always valid unless specified
Can use references but need to take advantage of lifetimes which ensures that the reference is valid whilst the structure is valid
This is valid but compiler will ask for lifetime specifiers
struct User {
active: bool,
username: &str,
email: &str,
sign_in_count: u64,
}
In short use data times that are owned rather than references
Adding Increased Functionality of Structs with derived traits
print can do many different types of formatting Cant print out structs by default because there are so many options with or without braces, commas, should all fields be shown This will cause an error
struct Rectangle {
length: u32,
width: u32,
}
let rect1 = Rectangle {
length: 8,
width: 4,
};
println!("rect1 contains {}", rect1);
{} tell println to use Display by default because there is only one way to show many primitive data types
{var_name:?} this is for the format Debug {var_name:#?} this is for pretty printing in Debug format, good for larger structs
Debug is also not implemented for the struct and therefore not supported
#[derive(Debug)]
struct Rectangle {
length: u32,
width: u32,
}
// snip
println!("rect1 contains {rect1:?}"); // single line print, in debug format, output: rect1 contains Rectangle { length: 8, width: 4 }
println!("rect1 contains {rect1:#?}"); // pretty print in debug format, output: rect1 contains Rectangle {
// length: 8,
// width: 4,
// }
Another way to output pretty debug format by default is dbg! macro this prints out the file and line number as well of where it was called and returns the ownership of the value this prints to the stderr output stream this takes ownership of values compaired to println! prints to stdout output stream
example of using dbg
fn main() {
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale),
height: 50,
};
dbg!(&rect1); // because it takes ownership need to pass in a reference
}
output [src/main.rs:10:16] 30 * scale = 60 [src/main.rs:14:5] &rect1 = Rectangle { width: 60, height: 50, }
Methods
Fucntions that are more closely related to structs
similar to functions decalred, param and output are all the same
run the code when the method is declared elsewhere
unlike functions they are defined in the context of a struct, an enum or a trait
first parameter is always self, which represents the instance of the struct that is is being called upon just like python methods
definition
struct Rectangle {
length: u32,
width: u32,
}
// implementation block for Rectangle used to define fn related to the struct
// put in this blcok so that the use case doesnt need to be searched
impl Rectangle {
// fn moved to here to that it has access to the instance with the self reference
// fn now closely related to the rect struct
// first param must be the type self: Self or &self which rust lets you shorthand
// self can be borrowed, mutably borrowed, or take ownership of self
// should always borrow unless need to transferownership or mutate the stored var
// &mut self for mutable version of selfs
// use self when you want to transform the self into something else
fn area (&self) -> u32 {
self.length * self.width
}
}
useage
let rect1 = Rectangle {
length: 8,
width: 4,
}
println!("The area of the reactangle is {} square units",
// method syntax to call the area func
// notice dont need any param in, already has access to self reference
rect1.area()
);
this provides method syntax and dont have to repeat the structure they effect one impl can house all the methods for a struct, we can have tthe same method name as field name just differenitate with the use of () this is for a mthod not the field
this is used in getters where you want read only access to a struct, you can make the field pravate but the method public
130