DEV Community

loading...
Cover image for Rust 2 - Memory and advanced structures

Rust 2 - Memory and advanced structures

petr7555 profile image Petr Janik Updated on ・5 min read

Move

// on stack
let x = 5;
let y = x;
println!("{} {}", x, y); // i32 implements Copy trait, therefore this is fine

// on heap
let s1 = String::from("hello");
let s2 = s1;
println!("s1 = {} s2 = {}", s1, s2); // error: borrow of moved value: `s1`
Enter fullscreen mode Exit fullscreen mode

Clone

[//if](//if) we want to duplicate anything on heap, we need to call clone() explicitly
let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);
Enter fullscreen mode Exit fullscreen mode

Returning ownership

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)
}
Enter fullscreen mode Exit fullscreen mode

Reference

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}
Enter fullscreen mode Exit fullscreen mode

Mutable reference

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}
Enter fullscreen mode Exit fullscreen mode

Basic rules

  • Unlimited number of references
  • ONLY ONE mutable reference
  • Reference must be valid/alive/existing
let mut s = String::from("hello");

let r1 = &s; // ok
let r2 = &s; // ok
let r3 = &mut s; // error: mutable borrow occurs here

println!("{}, {}, and {}", r1, r2, r3);
Enter fullscreen mode Exit fullscreen mode

...but

let mut s = String::from("hello");

let r1 = &s; // ok
let r2 = &s; // ok
println!("{} and {}", r1, r2);
// r1 and r2 are not used any more

let r3 = &mut s; // ok
println!("{}", r3);
Enter fullscreen mode Exit fullscreen mode

Slice

let s = String::from("hello world");

let hello = &s[0..5];  // hello
let world = &s[6..11]; // world
Enter fullscreen mode Exit fullscreen mode

Working with string references

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}
Enter fullscreen mode Exit fullscreen mode

Structures

Definition

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
Enter fullscreen mode Exit fullscreen mode

Usage

fn main() {
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    user1.email = String::from("anotheremail@example.com");
}
Enter fullscreen mode Exit fullscreen mode

Shortcut for struct creation

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}
Enter fullscreen mode Exit fullscreen mode

Creation from already existing struct

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    let user2 = User {
        email: String::from("another@example.com"),
        username: String::from("anotherusername567"),
        ..user1
    };
}
Enter fullscreen mode Exit fullscreen mode

Working with struct

#[derive(Debug)] // 'Debug' macro allows us to print the struct
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        area(&rect1)
    );

    println!("rect1 is {:?}", rect1); // here we use {:?} instead of empty {}
}

fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}
Enter fullscreen mode Exit fullscreen mode

Methods

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width >= other.width && self.height >= other.height
    }
}

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let sq = Rectangle::square(3);
    let rect = Rectangle {
        width: 3,
        height: 4,
    };

    println!("Can rect hold sq? {}", rect.can_hold(&sq));
}
Enter fullscreen mode Exit fullscreen mode

Enums

Simple enum

enum Delivery {
    Pickup,
    Parcel,
}

let delivery = Delivery::Pickup;
Enter fullscreen mode Exit fullscreen mode

Enums like tuple structs

enum Delivery {
    Pickup,
    Parcel(String),
}

let delivery = Delivery::Parcel(String::from("Queens, New York 11434"));
Enter fullscreen mode Exit fullscreen mode

Enums as c-like structures

// starting with 0
enum Number {
    Zero,
    One,
    Two,
}

enum AnotherNumber {
    Zero,      // 0
    Three = 3, // 3 
    Four,      // 4   
}

// enums with set value
enum Color {
    Red = 0xff0000,
    Green = 0x00ff00,
    Blue = 0x0000ff,
}

fn main() {
    // enum can be converted to integer
    println!("zero is {}", Number::Zero as i32);
    println!("one is {}", Number::One as i32);

    println!("roses are #{:06x}", Color::Red as i32);
    println!("violets are #{:06x}", Color::Blue as i32);
}
Enter fullscreen mode Exit fullscreen mode

Pattern matching

enum Delivery {
    Pickup,
    Parcel(String),
    PickInWarehouse,
}

fn main() {
    let delivery = Delivery::Pickup;
    deliver(delivery);
}

fn deliver(delivery: Delivery) {
    match delivery {
        Delivery::Pickup => {
            println!("Pick the parcel at our shop!");
        }
        Delivery::Parcel(address) => {
            println!("Parcel will be delivered to address: {}!", address);
        }
        _ => {
            println!("Not implemented delivery.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

More pattern matching!

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {:?}", y),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {:?}", x, y);
}
Enter fullscreen mode Exit fullscreen mode

Enums pattern matching

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.")
        }
        Message::Move { x, y } => {
            println!(
                "Move in the x direction {} and in the y direction {}",
                x, y
            );
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => println!(
            "Change the color to red {}, green {}, and blue {}",
            r, g, b
        ),
    }
}
Enter fullscreen mode Exit fullscreen mode

Multiple options

fn main() {
    let x = 1;

    match x {
        1 | 2 => println!("one or two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}
Enter fullscreen mode Exit fullscreen mode

Range

fn main() {
    let x = 'c';

    match x {
        'a'..='j' => println!("early ASCII letter"),
        'k'..='z' => println!("late ASCII letter"),
        _ => println!("something else"),
    }
}

// prints: early ASCII letter
Enter fullscreen mode Exit fullscreen mode

Touple

// '..' can only be used once per tuple pattern
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {}, {}", first, last);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Structs

fn main() {
    struct Point {
        x: i32,
        y: i32,
        z: i32,
    }

    let origin = Point { x: 0, y: 1, z: 2 };

    match origin {
        Point { y, .. } => println!("y is {}", y), // y is 1
    }
}
Enter fullscreen mode Exit fullscreen mode

if let

fn main() {
    let some_u8_value = Some(0u8);
    if let Some(3) = some_u8_value {
        println!("three");
    }
    // shortcut for
    match some_u8_value {
        Some(3) => println!("three"),
        _ => {}
    }
}
Enter fullscreen mode Exit fullscreen mode

Error handling

Types of errors

  • errors that can be handled
enum Result<T, E> {
    Ok(T),
    Err(E),
}
Enter fullscreen mode Exit fullscreen mode
  • errors after which there's no use in running the program any further
panic!("unrecoverable error")
Enter fullscreen mode Exit fullscreen mode

Opening file

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
}
Enter fullscreen mode Exit fullscreen mode

Handling specific errors

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {:?}", e),
            },
            other_error => {
                panic!("Problem opening the file: {:?}", other_error)
            }
        },
    };
}
Enter fullscreen mode Exit fullscreen mode

Panic immediately

use std::fs::File;

fn main() {
    // use unwrap when prototyping, when you are SURE the file exists
    // or as a shortcut for:
    //  let f = match f {
    //       Ok(file) => file,
    //       Err(error) => panic!(),
    //       };
    let f = File::open("hello.txt").unwrap();
    // with message
    let f = File::open("hello.txt")
        .expect("Problem opening the file hello.txt");
}
Enter fullscreen mode Exit fullscreen mode

Propagate error to calling function

use std::fs::File;
use std::io;
use std::io::{Read, Error};

fn main() {
    let result = read_username_from_file();
    match result {
        Ok(username) => println!("username: {}", username),
        Err(_) => println!("error reading username"),
    }
}

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("username.txt")?.read_to_string(&mut s)?;
    Ok(s)
}
Enter fullscreen mode Exit fullscreen mode

Vectors

fn main() {
    let v = vec![1, 2, 3];

    // or
    let mut v = Vec::new();
    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
}
Enter fullscreen mode Exit fullscreen mode

Get elements of vector

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let third: &i32 = &v[2]; // panicks when out of bounds
    println!("The third element is {}", third);

    match v.get(2) {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element."),
    }
}
Enter fullscreen mode Exit fullscreen mode

Iterating over vector

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{}", i);
    }

    // same as
    let mut iter = IntoIterator::into_iter(v);
    // or
    // let mut iter = v.iter();
    loop {
        match iter.next() {
            Some(i) => {
                println!("{}", i);
            },
            None => break,
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Mutating vector when iterating

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}
Enter fullscreen mode Exit fullscreen mode

Exercise

Task

Create a turn based game applying the concepts from this post.

Solution

Turn based game


Check out my learning Rust repo on GitHub!

Discussion

pic
Editor guide