Types and Conversion
Variables
// variables are immutable by default
let x = 5;
let mut x = 5;
// shadowing rather than mut
let x = 5;
// basically define a new var with the same name
let x = x + 1;
let x = x * 2;
// shadow can change var type (essentially a new var)
let s = " "; // string type
let s = s.len(); // int type
Generic Types
fn foo<T>(arg: T) {...}
struct GenVal<T>(T);
struct GenVal<T, U> {
gen_val: T,
gen_val_2: U,
}
enum Result<T, E> {
Ok(T),
Err(E), // e.g. std::io::Error
}
impl<T> SomeStruct<T>
where
T: Copy + Default + PartialEq + Add<Output=T>
{
fn new(value: T) -> SomeStruct<T> {}
}
Sized and Unsized
- defined in
std::marker::Sized which contains no methods or associated types
- A sized type is one whose values all have the same size in memory
T: Sized is the default impl and cannot explicitly impl
- Use
std::mem::size_of::<T>() to find out the size of sized types
String is 24 bytes; u8 is 1 byte; char is 4 bytes
std::mem::size_of_val(&T) will return the same if T is Sized
assert_eq!(24, mem::size_of::<String>());
assert_eq!(16, mem::size_of::<&str>());
assert_eq!(8, mem::size_of::<&String>());
- unsized types
str, array [T]; but &str and &[T] are sized
dyn T type (referent to trait object); but Box<dyn T> is sized
T: ?Sized needs to be explicitly marked
std::mem::size_of_val(&T) to
let s = "hello";
// size of str is its real length
assert_eq!(5, mem::size_of_val("hello"));
// size of &str is 16 bytes
assert_eq!(16, mem::size_of_val(&s));
let s = "hello".to_string();
// size of String is 24 bytes
assert_eq!(24, mem::size_of_val(&s));
let x: [u8; 13] = [1; 13];
// 13 of u8 (1 byte) is 13 bytes
assert_eq!(13, mem::size_of_val(&x));
let y: [u32; 16] = [1; 16];
// u32 is 4 bytes; 16 of u32 is 64 bytes
assert_eq!(64, mem::size_of_val(&y));
Primitives
// i8, i16, i32, i64, i128, isize
// u8, u16, u32, u64, u128, usize
let num: u32 = 42;
let num = 10u8;
let num = 10_u8;
// use _ to improve readability
let num = 100_000_000_i32;
// prefix 0x, 0o or 0b for hexa, octal or binary
ob0011u32
ox80u32
// methods can be used for instance or type
assert_eq!((-2_i32).abs(), i32::abs(-2));
// use div_euclid and rem_euclid
// calculate quotient ot Euclidean division of self
// and the least nonnegative remainder of self
let a: i32 = -7;
let b: 4;
assert_eq!(a.div_euclid(b), -2);
assert_eq!(a.rem_euclid(b), 1);
// BitAND and BitOR
// `&` and `|`
// bitwise and and or operator
// f32, f64
let y: f32 = 3.0;
// use float with caution
// use EPSILON as acceptable margin
if (float1 - float2).abs() < f64::EPSILON {
// do something
}
// float does not impl Ord, so cannot use sort() for slice of float
// use partial_cmp
let mut f = [0.1, 0.2, 0.6, 0.4];
f.sort_by(|a, b| a.partial_cmp(b).unwrap());
// addition, subtraction, multiplication, division, remainder (%)
// use `checked_`, `wrapping_`, `saturating_` or `overflowing_` for overflow
assert_eq!(10_u8.checked_add(20), Some(30));
assert_eq!(100_u16.wrapping_mul(200), 20000);
assert_eq!(32760_i16.saturating_add(10), 32767);
assert_eq!(255_u8.overflowing_sub(2), (253, false));
// A shift of 17 bits is too large for `u16`, and 17 modulo 16 is 1.
assert_eq!(5_u16.overflowing_shl(17), (10, true));
// true, false
let f: bool = false;
true && false
true || false
!true
// Rust does not automatically convert to Bool
// but can be casted to int; cannot cast int to Bool
assert_eq!(false as i32, 0);
assert_eq!(true as i32, 1);
let my_num = 100;
// print!: no new line
println!("{}", my_num as u8 as char); // print "d"
// use single quote for char
let c = 'c';
// b'c' as ASCII value
assert_eq!(99, b'c');
// inside (), separated by ,
// types dont have to be the same
// fixed length
// access by .
let tup: (i32, f64, u8) = (500, 6.4, 1);
let one = tup.1 // 6.4
// deconstruct
let (x, y, z) = tup;
let (head, tail) = "hello world".split_at(5);
// can only use constants as indices
tup.1 // this is ok
tup.i // doesnt work
// use [], separated by ,
// arrays needs to be init first before use
let i = [1,2,3,4,5];
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; // five elements of 3
let b = a[1];
// type signature &[T]; such as &[i32]
let s = String::from("a long string");
let a = &s[0..4];
let b = &s[2..];
// slices of arrays
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
// slices of vector
let v = vec![1,2,3,4,5,6];
let a = &v[0..4];
println!("{:?}", a);
&str is a slice of bytes: &[u8] with UTF-8
- String literal is a
&str that oints to preallocated text
// string literal, UTF-8
// will interpret \n as line break 0xA
"hello world!"
// raw string literal
// UTF-8; will not interprete \n
r"..."
// raw string literal
// can contain ", number of # can vary
r#"..."#
// byte string literal, construct ASCII [u8], not a string
b"..."
// raw byte string liateral, ASCII [u8]
br"...", br#"..."#
// single quote for char
'c'
// ASCII byte literal
b'x'
// new line is included
println!("In the room the women come and go,
Singing of Mount Abora");
// `\` will escape newline
// the following will print in one line
println!("It was a bright, cold day in April, and \
there were four of us—\
more or less.");
// raw string starts with `r` and `r#`
let default_win_install_path = r"C:\Program Files\Gorillas";
println!(r###"
This raw string started with 'r###"'.
Therefore it does not end until we reach a quote mark ('"')
followed immediately by three pound signs ('###'):
"###);
// byte string, starts with b
// of type `&[u8]`
// raw byte strings start with `br`
let m = b"GET";
assert_eq!(m, &[b'G', b'E', b'T']);
Constants and Global Variables
const: value is compiled into code
- Use constants for magic numbers and strings
- No
mut
static: variable that is set up before probram starts and lasts till end
- Use statics for larger amount of data, or need to borrow a ref to the const value
- Can be
mut, requires unsafe - or use inferior mutability
- Can only be init by constant functions, tuple structs and tuple variants
- Use
lazy_static or OnceCell
- Global variables can only be declared with
const or static
- use
lazy_static, OnceCell, or thread_local (TODO)
//const
// unchangeable value
// must annotate type
// cannot set from a function
const THRESHOLD: i32 = 10;
//static
// possibly mut var with 'static lifetime
static LANGUAGE: &str = "Rust";
// lazy_static crate
lazy_static! {
static ref SET: Mutex<HashMap::<u32>> = Mutex::new(HashMap::new());
}
fn main {
SET.lock().unwrap().insert(0);
}
// Once Cell
// safe init of global data
static INSTANCE: OnceCell<Logger> = OnceCell::new();
//lazy init global data
static INSTANCE: OnceCell<Mutex<HashMap<i32, String>>> = OnceCell::new();
// or use once_cell::sync::lazy (use unsync for single thread)
static GLOBAL_DATA: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| {
let mut m = HashMap::new();
m.insert(13, "Spica".to_string());
m.insert(74, "Hoyten".to_string());
Mutex::new(m)
});
Default »
- Derivable
#[derive(Default)]
- Trait:
Default
- Primitives types have default value
fn default() -> Self;
let some_struct: SomeStruct = Default::default();
Ordering and Comparison »
PartialEq and Eq
PartialEq does not require x == x
- But ensures
x == y means y == x; and x == y with y == z means x == z
Eq is a subtrait of PartialEq
- Only
f31 and f64 do not implement Eq
- Both takes references, and not move the value
- Both derivable
trait Eq: PartialEq<Self> {}
Struct
// CamelCase
// fields separated by ,
// no ; at the end of definition
struct User {
username: String,
email: String,
id: u64,
active: bool,
}
let mut user1 = User {
email: String::from("mail@examplae.com"),
username: String::from("john"),
id: 1,
active: true,
};
user1.email = String::from("another@email.com");
let user2 = User {
username: String::from("johnny");
..user1 // other fields same as user1, no comma
};
// if parameter name and field name are the same, use shorthand syntax
let username = String:from("john");
let user3 = User {
username,
// - snip --
};
// println!
// {:?} debug trait
// {:#} pretty-print
// {:#?}
#[derive(Debug)]
struct SomeStruct {...};
println!("{:?}", some_struct);
// Generic Struct
struct SomeName<T> {
field1: Vec<T>.
field2: u32,
}
// impl needs to have <T> too
impl<T> SomeName<T> { ... }
// lifetime needed if a struct type contains ref
struct SomeStruct<'a> {
some: &'a i32,
more: i32,
}
Struct methods
impl StructName {
fn method(&self, other: &Other_type) -> type {
self.field * self.field2
}
}
// use dot to call method
some_struct.method(&s1, &s2)
// `&self` can also be `Box<Self>`, `Rc<Self>`, or `Arc<Self>`
let mut s = Box::new(String::from("test"));
s.push('c');
// associated functions
// dont take self as a parameter
// often used to return a new instance of the struct
impl Rect {
fn square(size: u32) -> Rect {
Rect {
width: size,
height: size,
}
}
}
let sq = Rect::square(5);
// Associated Consts
// values associated with a type rather than an instance
pub struct Vector2 {
x: f32,
y: f32,
}
impl Vector2 {
const ZERO: Vector2 = Vector2 { x: 0.0, y: 0.0 };
const UNIT: Vector2 = Vector2 { x: 1.0, y: 1.0 };
// does not have to the same type
const NAME: &'static str = "Some Name";
const ID: u32 = 1;
}
// use it similar to associated function
let s = Vector2::UNIT.some_fn();
Tuple Struct
// no names associated with fields
// just types; types can be different
// need ; at the end
struct Color(i32, i8, u32);
let black = Color(0, 0, 0);
// best used for *newtypes*
// stricter type checking
struct Ascii(Vec<u8>);
Unit Struct
// field-less, useful for generics
struct Unit;
// only one value
let o = Unit;
Enum
- struct contains fields
- enum contains variants
- enum variants can be unit, tuple or struct
- Useful crates:
enum CaseCamelName {
Variant, // unit
Variant1 { x: i32, y: i32 }, // struct
Variant3(String), // tuple
Variant4(SomeStruct),
}
let m = SomeEnum::Variant3(some_string);
impl SomeEnum {
fn method(&self) {
match self {
variant1 => expr,
...
}
}
// can use variants directly if `use`
use SomeEnum;
match some_enum {
Variant => { /* something */ }
_ => { /* something else */ }
}
// enum unit variant can be stored as integers
// default starting at 0
// can be manually set
enum HttpStatus {
Ok = 200,
NotModified = 304,
NotFound = 404,
...
}
// impl Debug, Unit variant can be printed
#[derive(Debug)]
enum SomeEnum {
Field,
...
}
assert_eq!("Field".to_string(), format!("{:?}", SomeEnum::Field);
// Generic data structure
// define a `BinaryTree` type that cna store any number of values of type `T`
// An ordered collection of `T`s.
enum BinaryTree<T> {
Empty,
NonEmpty(Box<TreeNode<T>>),
}
// A part of a BinaryTree.
struct TreeNode<T> {
element: T,
left: BinaryTree<T>,
right: BinaryTree<T>,
}
Cast and Alias
Casting with as
// casting between primitive types
let integer = decimal as u8;
let character = integer as char;
// float cannot be converted to a char
Aliasing with type
// UpperCamelCase names
type NanoSecond = u64;
let ns: NanoSecond = 5 as u64;
Wrap a type with Struct
// std::path::Path is a wrapper of `OsStr`
pub struct Path {
inner: OsStr,
}
// std::path::PathBuf is a wrapper of `OsString`
pub struct PathBuf {
inner: OsString,
}
struct Ascii(Vec<u8>);
Conversion
pub trait AsRef<T>
where
T: ?Sized,
{
fn as_ref(&self) -> &T;
}
// blanket impl
impl<T: ?Sized, U: ?Sized> AsRef<U> for &T
where
T: AsRef<U>,
{
fn as_ref(&self) -> &U {
<T as AsRef<U>>::as_ref(*self)
}
}
// trait bound `AsRef<str> will accept all references
// that can be converted to `&str`
fn is_hello<T: AsRef<str>>(s: T) {
// s is of type T; s.as_ref() is &str
assert_eq!("hello", s.as_ref());
}
let s = "hello";
is_hello(s);
let s = "hello".to_string();
is_hello(s);
std::convert::From and TryFrom Traits
- Consuems the value and converts to another type
- Provides
::from() -> Self and ::try_from() -> Result<Self, Self::Error>
- Automatically provides
.into() -> T and .try_into() -> Result<T, Self::Error>
pub trait From<T> {
fn from(T) -> Self;
}
pub trait TryFrom<T> {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
// examples
String::from("hello");
"hello".to_string();
assert!(i32::try_from(100_000_000 as i64).is_ok()); // i32 implemented `TryFrom` i64
- Implement
From for Custom Error type for better error handling
enum CliError {
IoError(io::Error),
ParseError(num::ParseIntError),
}
impl From<io::Error> for CliError {
fn from(error: io::Error) -> Self {
CliError::IoError(error)
}
}
impl From<num::ParseIntError> for CliError {
fn from(error: num::ParseIntError) -> Self {
CliError::ParseError(error)
}
}
// `?` operator can automatically convert to custom error type
// by calling `Into<CliError>::into` which is auto provided by `From`
fn open_and_parse_file(file_name: &str) -> Result<i32, CliError> {
let mut contents = fs::read_to_string(&file_name)?;
let num: i32 = contents.trim().parse()?;
Ok(num)
}