Rust examples: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 336: Line 336:
*Only functions allowed are displayed in the IDE. E.g. a locked password manager cannot see the functions for an unlocked password manager
*Only functions allowed are displayed in the IDE. E.g. a locked password manager cannot see the functions for an unlocked password manager
*The state PhantomData<State> is compiled out and reduces memory footprint
*The state PhantomData<State> is compiled out and reduces memory footprint
=Recursion and Factorial=
==General==
Recursion is when we use a function multiple times but change the input. An example of recursion is factoral where
    n! = n * n-1 * n -2 * n-2...
    n! = n * (n-1)!
when doing recursion we must specify a any base cases and a termination condition. For factorial this would be line 12
<syntaxhighlight lang="c" highlight="12">
#include<stdio.h>
long int multiplyNumbers(int n);
int main() {
    int n;
    printf("Enter a positive integer: ");
    scanf("%d",&n);
    printf("Factorial of %d = %ld", n, multiplyNumbers(n));
    return 0;
}
long int multiplyNumbers(int n) {
    if (n>=1)
        return n*multiplyNumbers(n-1);
    else
        return 1;
}
</syntaxhighlight>
==Call Stack==
When you use recursion we need to be careful that we do not have a stackoverflow.<br>
[[File:Screenshot from 2024-01-11 18-51-05.png |500px]]
==Base Cases==
Base Case is defined as the condition in Recursive Function, which tells the function when to stop. It is the most important part of every Recursion, because if we fail to include this condition it will result in '''INFINITE RECURSIONS'''.
==Fibonacci==
Here is the Fibonacci example which seems to be the darling of all CS students. Here there are 2 bases cases
*base case when n = 0
*base case when n = 1
I think the match operator is a great way to express recursive functions
<syntaxhighlight lang="rs">
fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 1,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}
fn main() {
    println!("Fibonacci generator");
    println!("{}", fibonacci(1));
    println!("{}", fibonacci(3));
    println!("{}", fibonacci(5));
}
</syntaxhighlight>
==Palindrome==
Here we decide what the parameters are for the base cases
<syntaxhighlight lang="rs">
fn is_palindrome(s: &str, start: usize, end: usize) -> bool {
    if start >= end {
        return true;
    }
    if s.chars().nth(start) != s.chars().nth(end) {
        return false;
    }
    is_palindrome(s, start + 1, end - 1)
}
fn main() {
    let answer1 = is_palindrome("racecar", 0, 6);
    println!("{}", answer1);
    let answer2 = is_palindrome("xracecar", 0, 7);
    println!("{}", answer2);
}
</syntaxhighlight>
==Tower of Hanoi==
This was a more challenging puzzle to demonstrate recursive functions. Here the object of the game is to move the blocks from tower one to tower three and replicate the order. The rules are
*Only one block at a time
*Larger blocks cannot be placed on smaller blocks
I used the youtube video [[https://www.youtube.com/watch?v=rf6uf3jNjbo here]] which provided great graphics on how to solve this. Here is a function for 3 blocks and the steps required to achieve it.<br>
[[File:Hanoi.jpg |400px]]<br>
Took me a while to understand but the steps (not the moves were)
*Move n-1 Discs from A to B using C
*Move a Disc from A to C
*Move n-1 Discs from B to C using A
So in rust we can see the number of moves based on number of discs (n) is
<syntaxhighlight lang="rs">
fn toh(n: i32) -> i32 {
  if n = 0 {
      return 0
  }
         
  return toh(n-1) // Step 1 n-1 Discs)     
          + 1      // Step 2 1 disc
          toh(n-1) // Step 3 n-1 Discs)     
}
fn main() {
    println!("{}", toh(4)); // 15
}
</syntaxhighlight>
==Sum of Triangles==
This is where we start with the base and the next row is the some of it child numbers. All my own work this time.
<syntaxhighlight lang="rs">
fn sum_of_triangles(numbers: &mut Vec<i32>, current_sum: i32) -> i32 {
    println!("Input Current Sum {:?}", current_sum);
    // Empty return
    if numbers.len() == 1 {
        return current_sum;
    }
    // Create a new vector
    let mut new_vector = Vec::<i32>::new();
    // Iterate through the vector - 1
    for n in 0..numbers.len() -1 {
        new_vector.push(numbers[n] + numbers[n+1])
    }
    // Sum up
    let new_sum = new_vector.iter().sum::<i32>();
    sum_of_triangles(&mut new_vector, new_sum)
}
fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];
    let current_sum = numbers.iter().sum::<i32>();
    let answer3 = sum_of_triangles(&mut numbers, current_sum);
    println!("This is the answer {}", answer3);
}
</syntaxhighlight>

Revision as of 02:28, 10 October 2024

Introduction

The reason for this page is because MediaWiki gets a bit testy with over 1000 lines grrhhhhh so bits of the rust page are on here

Handling CLI Arguments

Clap seems to be the approach for this, make sure you install the macros otherwise errors will show on the derive macro.

cargo add clap --features derive

And the code. The shows how to replace text from a file to another file.

use regex::Regex;
use text_colorizer::*;
use clap::Parser;
use std::fs;

#[derive(Parser,Debug)]
struct Args {
    #[clap(short, long)]
    pattern: String,
    #[clap(short, long)]
    replace: String,
    #[clap(short, long)]
    input_file: String,
    #[clap(short, long)]
    output_file: String,
}


fn replace (pattern: &str, replace: &str, data: &str) -> Result<String, regex::Error> {
    let regex = Regex::new(pattern)?;
    Ok(regex.replace_all(data, replace).to_string())
}

fn read_write_file(args: Args) {

    let data = match fs::read_to_string(&args.input_file) {
        Ok(data) => data,
        Err(err) => {
            eprintln!("{} failed to read from file {}: {:?}", 
                "Error".red().bold(), 
                args.input_file, err);
            std::process::exit(1);
        }
    };

    let replace_data = match replace(&args.pattern, &args.replace, &data) {
        Ok(data) => data,
        Err(err) => {
            eprintln!("{} failed to replace text {:?}", 
                "Error".red().bold(), 
                err);
            std::process::exit(1);
        }
    };

    match fs::write(&args.output_file, replace_data) {
        Ok(_) => {},
        Err(err) => {
            eprintln!("{} failed to write to file {}: {:?}", 
                "Error".red().bold(), 
                args.output_file, err);
            std::process::exit(1);
        }
    };
}

fn main_body(args: Args) -> Result<(), ()> {
    // Your main body here
    println!("input_file {}", args.input_file);
    println!("output_file {}", args.output_file);
    read_write_file(args);
    Ok(())
}

fn main() {
    let args = Args::parse();
    main_body(args).unwrap_or_else(|_| {
        eprintln!("Error");
        std::process::exit(1);
    });
}

Implementing Your Types

Another letsgetrusty. This adviced when make a library to play nice by implmenting

  • Debug, Clone, Default, PartialEq

It suggested you consider also implementing

  • JSON serialize ,deserialize

This code showed me how to optionalize it.

use std::{rc::Rc, sync::Arc};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Role {
    Admin,
    Standard,
    #[default]
    Guest,
}

#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
struct DB {}

#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct User {
    id: u32,
    name: String,
    role: Role,
    #[cfg_attr(feature = "serde", serde(skip))]
    db: Arc<DB>,
}

fn main() {
    let user = User {
        id: 123,
        name: "Bogdan".to_owned(),
        role: Role::Admin,
        db: Arc::new(DB {}),
    };

    println!("{:?}", user);

    let user2 = user.clone();

    println!("{:?}", user2);

    let guest = User::default();

    let guest2 = User::default();

    assert_eq!(guest, guest2);

    let user_str = "{ \"id\": 123, \"name\": \"Bogdan\", \"role\": \"Admin\" }";

    #[cfg(feature = "serde")]
    let user: User = serde_json::from_str(&user_str).unwrap();

    #[cfg(feature = "serde")]
    println!("{:?}", user);
}

fn is_normal<T: Sized + Send + Sync + Unpin>() {}

#[test]
fn normal_types() {
    is_normal::<User>();
}

And here is the toml

[package]
name = "common-traits"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1.0", features = ["derive", "rc"], optional = true }
serde_json = "1.0"

[features]
serde = ["dep:serde"]

=Date Handling in Rust Found the on twitter (not all bad) @orhanbalci Type conversion and dates are the worst in most languages so thought I would keep this.

fn main() {
    // you can use chrono crate for date time operations.
    // chrono crate is more capable than standard library std::time module
    use chrono::prelude::*;
    use chrono::Duration;
    
    // you can retrieve current date in Utc timezone as follows
    let current_date = Utc::today();
    println!("Utc current date: {}", current_date);
    
    // you can retrieve current date in local timezone as follows
    let local_current_date = Local::today();
    println!("Local current date: {}", local_current_date);
    
    // you can retrieve current time in UTC as follows
    let current_time_utc = Utc::now();
    println!( "Utc current time: {}", current_time_utc);
    
    // you can retrieve current time in local time zone as follows
    let current_time_local = Local::now();
    println!("Local current time: {}", current_time_local);
    
    // you can add some duration to a chrono::Date
    // succ method gets succeeding date
    
    let today = Utc::today();
    
    let tomorrow = today + Duration::days(1);
    assert_eq!(today.succ(), tomorrow);
    
    // you can subtract some duration from a chrono
    // pred method gets previous date
    
    let today = Utc::today();
    
    let yesterday = today - Duration::days(1);
    assert_eq!(today.pred(), yesterday);
    
    // you can get UNIX timestamp (epoch) value for a datetime using
    
    // timestamp method of chrono::offset::TimeZone trait.
    
    // since timestamps have numeric representation, they are easy to store in db
    // and send through network. You can aldo prefer this notation in your APIs
    let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 0);
    assert_eq!(dt.timestamp(), 1);
    
    //you can also get timestamp value of a datetime in milliseconds
    let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 500);
    assert_eq!(dt.timestamp_millis(), 1500);
    
    //you can convert create a chrono::DateTime from a timestamp seconds
    let timestamp = 15;
    
    let datetime = Utc.timestamp(timestamp, 0);
    assert_eq!(datetime.timestamp(), 15);
    
    //you can get difference of two date times as follows
    let first = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 0);
    let second = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 2, 0);
    
    let difference: Duration = second.signed_duration_since(first);
    assert_eq!(difference, Duration::seconds(1));
    
    // you can also add and subtract duration from a DateTime struct
    
    let now = Utc::now();
    
    let yesterday_at_the_same_time = now - Duration::days(1);
    println!("Yesterday at the same time: {}", yesterday_at_the_same_time);
    
    //you can compare durations
    assert_eq! (Duration::days(1), Duration::hours(24));
    
    //you can format your DateTime struct using strftime formatting options
    let now = Utc::now();
    println!("{}", now. format( "%d.%m.%Y %H:%M"));
    
    //you can parse DateTime from a formatted string as below
    let some_time = NaiveDateTime::parse_from_str("01.10.2021 14:21", "%d.%m.%Y %H:%M").unwrap( );
    assert_eq!(some_time, NaiveDate::from_ymd(2021,10,1).and_hms(14,21,0));
}

Type State Pattern

In some cases we only want to be able to do some functions on a struct based on the state of the struct. In rust we could create a field in the struct called state and maintain which functions are allowed based on the current state. However rust provides a better approach referred to as the type state pattern. For example if we wanted make a password manager structure we could do this.

#![allow(unused)]

use std::collections::HashMap;
use std::marker::PhantomData;

struct Locked;
struct Unlocked;

// PasswordManager<Locked> != PasswordManager<Unlocked>

struct PasswordManager<State = Locked> {
    master_pass: String,
    passwords: HashMap<String, String>,
    state: PhantomData<State>,
}

// Locked Functions
impl PasswordManager<Locked> {
    pub fn unlock(self, master_pass: String) -> PasswordManager<Unlocked> {
        PasswordManager {
            master_pass: self.master_pass,
            passwords: self.passwords,
            state: PhantomData,
        }
    }
}

// Unlocked Functions
impl PasswordManager<Unlocked> {
    pub fn lock(self) -> PasswordManager<Locked> {
        PasswordManager {
            master_pass: self.master_pass,
            passwords: self.passwords,
            state: PhantomData,
        }
    }

    pub fn list_passwords(&self) -> &HashMap<String, String> {
        &self.passwords
    }

    pub fn add_password(&mut self, username: String, password: String) {
        self.passwords.insert(username, password);
    }
}

// Generic Functions
impl<State> PasswordManager<State> {
    pub fn encryption(&self) -> String {
        todo!()
    }

    pub fn version(&self) -> String {
        todo!()
    }
}

// Constructor
impl PasswordManager {
    pub fn new(master_pass: String) -> Self {
        PasswordManager {
            master_pass,
            passwords: Default::default(),
            state: PhantomData,
        }
    }
}

fn main() {
    let mut manager = PasswordManager::new("password123".to_owned());
    let manager = manager.unlock("password123".to_owned());
    manager.list_passwords();
    manager.lock();
}

The benefits to doing this are

  • Only functions allowed are displayed in the IDE. E.g. a locked password manager cannot see the functions for an unlocked password manager
  • The state PhantomData<State> is compiled out and reduces memory footprint

Recursion and Factorial

General

Recursion is when we use a function multiple times but change the input. An example of recursion is factoral where

   n! = n * n-1 * n -2 * n-2...
   n! = n * (n-1)!

when doing recursion we must specify a any base cases and a termination condition. For factorial this would be line 12

#include<stdio.h>
long int multiplyNumbers(int n);
int main() {
    int n;
    printf("Enter a positive integer: ");
    scanf("%d",&n);
    printf("Factorial of %d = %ld", n, multiplyNumbers(n));
    return 0;
}

long int multiplyNumbers(int n) {
    if (n>=1)
        return n*multiplyNumbers(n-1);
    else
        return 1;
}

Call Stack

When you use recursion we need to be careful that we do not have a stackoverflow.

Base Cases

Base Case is defined as the condition in Recursive Function, which tells the function when to stop. It is the most important part of every Recursion, because if we fail to include this condition it will result in INFINITE RECURSIONS.

Fibonacci

Here is the Fibonacci example which seems to be the darling of all CS students. Here there are 2 bases cases

  • base case when n = 0
  • base case when n = 1

I think the match operator is a great way to express recursive functions

fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 1,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

fn main() {
    println!("Fibonacci generator");
    println!("{}", fibonacci(1));
    println!("{}", fibonacci(3));
    println!("{}", fibonacci(5));
}

Palindrome

Here we decide what the parameters are for the base cases

fn is_palindrome(s: &str, start: usize, end: usize) -> bool {
    if start >= end {
        return true;
    }

    if s.chars().nth(start) != s.chars().nth(end) {
        return false;
    }

    is_palindrome(s, start + 1, end - 1)
}

fn main() {
    let answer1 = is_palindrome("racecar", 0, 6);
    println!("{}", answer1);
    let answer2 = is_palindrome("xracecar", 0, 7);
    println!("{}", answer2);
}

Tower of Hanoi

This was a more challenging puzzle to demonstrate recursive functions. Here the object of the game is to move the blocks from tower one to tower three and replicate the order. The rules are

  • Only one block at a time
  • Larger blocks cannot be placed on smaller blocks

I used the youtube video [here] which provided great graphics on how to solve this. Here is a function for 3 blocks and the steps required to achieve it.

Took me a while to understand but the steps (not the moves were)

  • Move n-1 Discs from A to B using C
  • Move a Disc from A to C
  • Move n-1 Discs from B to C using A

So in rust we can see the number of moves based on number of discs (n) is

fn toh(n: i32) -> i32 {
   if n = 0 {
      return 0
   }
          
   return toh(n-1) // Step 1 n-1 Discs)      
          + 1      // Step 2 1 disc
          toh(n-1) // Step 3 n-1 Discs)      
}

fn main() {
    println!("{}", toh(4)); // 15
}

Sum of Triangles

This is where we start with the base and the next row is the some of it child numbers. All my own work this time.

fn sum_of_triangles(numbers: &mut Vec<i32>, current_sum: i32) -> i32 {

    println!("Input Current Sum {:?}", current_sum);

    // Empty return
    if numbers.len() == 1 {
        return current_sum;
    }

    // Create a new vector
    let mut new_vector = Vec::<i32>::new();

    // Iterate through the vector - 1
    for n in 0..numbers.len() -1 {
        new_vector.push(numbers[n] + numbers[n+1])
    }

    // Sum up 
    let new_sum = new_vector.iter().sum::<i32>();

    sum_of_triangles(&mut new_vector, new_sum)
}

fn main() {

    let mut numbers = vec![1, 2, 3, 4, 5];
    let current_sum = numbers.iter().sum::<i32>();

    let answer3 = sum_of_triangles(&mut numbers, current_sum);
    println!("This is the answer {}", answer3);
}