Rust :: Types and Conversion

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

assert_eq!(24, mem::size_of::<String>());
assert_eq!(16, mem::size_of::<&str>());
assert_eq!(8, mem::size_of::<&String>());
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);
// 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
// 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 »

fn default() -> Self;
let some_struct: SomeStruct = Default::default();

Ordering and Comparison »

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

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);
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
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)
}