Rust datatypes

From wikinotes
Revision as of 00:21, 10 February 2023 by Will (talk | contribs) (→‎Pointers)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Rust does not use null, instead is uses Option and Result.

Documentation

primitives https://doc.rust-lang.org/std/#primitives
custom smart pointers https://doc.rust-lang.org/stable/book/ch15-02-deref.html

Literals

General

'a'        // char
"abc"      // string (immutable)
1234       // i32
3.14       // f32
true/false // bool

Numeric Representations

1_000      // == 1000
1.000_000  // == 1.000000

0xff          // hex
Oo644         // octal
0b1111_0000   // binary
b'A'          // byte (u8)

Type Suffixes

1u8          // '1' as a u8
1i64         // '1' as a i64

Primitives

Text

str

  • immutable
  • size-known at compile-time

https://doc.rust-lang.org/std/primitive.str.html

let name: &str = "vaderd";     // assign string
let mut user = String::new();  // utf8 string

// type casting '&str' to 'i8'
let num: i8 = "123"
  .parse()
  .unwrap();

"foo".to_string() // type cast '&str' to 'String' (heap)

Some Useful Methods

"abc".len()            // 3  number of bytes used
"abc".ends_with("bc")  // true if ends with

// bytes-array to &str
let hello_bytes: [u8; 5] = [72, 69, 76, 76, 79];
let hello = std::str::from_utf8(&hello_bytes[..]).unwrap(); // 'HELLO'

char

chars refer to a single character, and it's literals use single-quotes.
chars use 4-bytes in memory; they can store multibyte characters.

https://doc.rust-lang.org/std/primitive.char.html

let foo: char = 'a';

String

String types are strings with a string-size that is unknown at compile-time.

let mut my_str = String::from("foo")
let mut my_str = "foo".to_string()

my_str.as_str(); // get string-slice from string (reference)

// string concatenation
my_str.push_str("bar");  // foobar   (push &str)
my_str.push('!')         // foobar!  (push char)

let s1 = String::from("foo")
let s2 = String::from("bar")
let s3 = s1 + &s2            // 's1' dropped, b/c ownership changes
let s3 = format!("{s1}{s2}") // 's1' not dropped, b/c params are references

partial strings, and representation. See representation types
to final representation applying diacritics is not in standard library.

// utf-8 has multibyte `scalar` characters
// `scalar` characters may be `diacritics`, 
// intended as modifiers for characters that follow.
 
let s: &str = &name[0..4]    // str from first 4x bytes (panic if not full unicode glyph)
"123".chars()                // enumerable of `scalar` chars (incl. diacritics)
"123".bytes()                // enumerable of `bytes` for chars

Numbers

implied type let var = 12;
assigned type let var: i8 = 12;
type suffix let var = 12i8;

int

  • signed integers range is split in two, can be positive/negative
  • unsigned integers are positive, and use all available bits
  • use radix to calculate max size that can be accomodated with b bits
// signed integers, by bit-size
i8     //        -128..127
i16    //      -32768..32767
i32    // -2147483648..2147483647
i64    // ...
i128
isize  // your CPU wordsize (ex. i32 or i64)

// unsigned integers, by bit-size
u8    // 0..255
u16   // 0..65535
u36   // 0..4294967295
u64   // ...
u128
usize // your CPU wordsize (ex. u32 or u64)

float

f32
f64

bool

true
false

fn foo(b: bool) { ... }

Collections

tuples

  • heterogenous
  • non-resizable
  • not stored in contiguous memory
  • support nesting

https://doc.rust-lang.org/std/primitive.tuple.html#

let var: (i8, char, u32) = (5, 'a', 300);
var = (1, "two", 3.14)
var.0  // item at index 1

arrays

  • homogenous
  • non-resizable
  • stored in contiguous memory

https://doc.rust-lang.org/std/primitive.array.html

// initialization
let var: [i32; 4] = [1, 2, 3, 4];  // declare an array of 4x 32-bit integers
let var: [i32; 4] = [100; 4];      // initialize all 4x ints as 100

// methods
var[0]     // 1
var.len()  // 4

// slices
let foo = &var[1..2];   // [2, 3]
println!("{}", foo[0]); // 2

slices

slices are a subsection of an array

let var: [i8; 4] = [1, 2, 3, 4];

let first_two = &var[0..1];   // [1, 2]
let first_two = &var[..1];    // [1, 2]
println!("{}", foo[0]); // 2

fn foo(s: &[i32]) { ... }               // borrows slice of an i32 array
fn foo(s: String) -> &str { ... }       // returns slice of String
fn foo(nums: &[usize; 10]) -> &[usize]  // returns slice of array (slice indexes always usize)

vectors

vectors are essentially resizable arrays.

  • homogenous
  • resizable
  • stored in contiguous memory

https://doc.rust-lang.org/std/vec/index.html

let v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3, 4, 5];

v[0]       // get value at index 0 (and panic if index is invalid)
v.get(0)   // return an Option (don't panic if index is invalid)
v.push(34) // append to vector

structs

Structs, along with each of their fields, are private by default.
To access outside of their module structs/fields must be defined with the pub keyword.

Regular Structs

struct Point { x: u8, y: u8 }
let p: Point = Point { x: 5, y: 10 };  // assignment
println!("point({}, {})", p.x, p.y);   // access fields with '.'

Structs have syntactic sugar so that you can reuse parameters for assignment.

struct Coord { x: u8, y: u8, z: u8 };
fn build_coord_at_x0(x: u8, y: u8) {
    Coord{
        x: 0,
        y,
        z
    }  // bind `y`, `z` values from matching params
}

There is also syntactic sugar for creating a struct from another of the same type,
only replacing select values.

struct Coord { x: u8, y: u8, z: u8 };

let p1 = Coord{x: 1, y: 2, z: 3};
let p2 = Coord{x: 5, ..p1};         // reuse fields from `p1` for all values except `x`

Tuple Structs

struct Color(i8, i8, i8);             // declaration
let c: Color = Color(100, 150, 200);  // instantiation
c.1 = 200;                            // assignment (uses `.` index)

Unit Structs

Unit structs have no value, they're just a type.

struct Centimeters;
let cm = Centimeters;

General

TODO:

not sure this really belongs here..

Debug trait

#[derive(Debug)]
struct Point{x: i8, y: i8}
let p = Point{x: 1, y: 2};

println!("{:?}", p);  // print all fields on struct
println!("{:#?}", p); // pretty-print struct
dbg!(p)               // prints the file, lineno, expression, and result

Access Control

# users.rs
pub struct User {   // `struct` is public, so type can be returned outside of module
    pub id u8,      // `id` is public, outside of module can access
    name String,    // `name` is private, outside of module cannot assign or access
}

enums

TODO:

  • are enums ordered? can you compare using < ?
  • how to retrieve tuple/struct from enum outside of a match ?

Enum Basics

Enums in rust are used to enumerate all possible values,
but unlike many other languages, they can be parametrized and bind an arbitrary value.
memory for enums will be allocated in chunks of the largest possible enum value.

enum TaskStatus {
  Blocked,
  Ready,
  Started,
  Finished,
}

TaskStatus::Ready

You can add enum-values to current scope (without namespace) with use keyword.

use TaskStatus::*;
let foo = Blocked;

use TaskStatus::{Blocked, Ready};
let foo = Blocked;
let bar = Started;  // raises error, since not in scope

You can cast enums to integers

let index = TaskStatus::Ready as u32;  // 1

Enum Params

You can also store complex information in an enum,
encoding additional information with each possible enum value.

enum Event {
  KeyPress(char),            // wrapped type is tuple
  Click { x: i32, y: i32 },  // wrapped type is struct
  Blue = 0x0000ff,           // wrapped type is primitive
}

Event::KeyPress('j')
Event::Click{x: 100, y: 900}

You can use the match keyword to extract information from these parametrized enum values.

#[derive(Debug)]
enum Pets {
    Cat(String, u8),
    Dog(String, u8, String),
}
let pet = Pets::Cat("maize", 1)

let result = match pet {
    Pets::Cat(name, age)        => { format!("A cat w/ name={name} age={age}", name=name, age=age) },
    Pets::Dog(name, age, breed) => { format!("A {breed} dog, name={name}, age={age}", name=name, age=age, breed=breed) }
}
// "A cat w/ name=maize age=1"

Methods

Like with structs, you can bind rust methods to enums.

enum Status {
    Ready,
    Started,
    Skipped,
    Completed,
}

impl Status {
    fn finished(&self) -> bool {
        match *self {
            Status::Completed | Status::Skipped => true;
            _ => false;
        }
    }
}

let s = Status::Skipped;
println!("{}", s.finished)

hashmaps

  • homogenous
  • heap allocated
  • un-ordered
  • objects implementing Copy trait will be copied, otherwise ownership given to map

https://doc.rust-lang.org/std/collections/struct.HashMap.html

use std::collections::HashMap;

let mut users = HashMap::new();

users.insert(0, "will");            // add/override entry to hash
users.entry(1).or_insert("alex");   // add entry to hash if not exist
let v: Option<&i32> = users.get(&0) // get entry from hash
for (key, val) in &users { ... }    // iterate over entries

Pointers

The basics are similar to most other languages:

let foo = String::new("hi");  // allocated on the heap
&foo                          // `&` get reference to foo

let myref = &foo;
*myref                        // `*` de-reference to get foo instance

However, mostly due to rust's ownership concepts there are some other semantics to handle edge cases.
See rust pointers for more details.

Other

Option

The Option type (enum) stands in for null in rust.
It is commonly used with match to extract the values.
It's methods are added to the prelude scope, so they are accessible without the namespace.

https://doc.rust-lang.org/std/option/enum.Option.html

let val = Some("foo");   // `Option` instance, with value present
let val = None;          // `Option` instance, standin for null

match val {
    Some(x) => println!("value was assigned"),     // `x` here refers to whatever value is held by `Some`
    None    => println!("value was not assigned"),
}

Result

Results are chainable enums with success/failure values.
For more details see rust errors.