Rust examples
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
Example Parsing Data with Rust and handling Errors With map_err and Closures
This is taken from Rustlings and here to remind me of the tricks
// Steps:
// 1. Split the given string on the commas present in it.
// 2. If the split operation returns less or more than 2 elements, return the
// error `ParsePersonError::BadLen`.
// 3. Use the first element from the split operation as the name.
// 4. If the name is empty, return the error `ParsePersonError::NoName`.
// 5. Parse the second element from the split operation into a `u8` as the age.
// 6. If parsing the age fails, return the error `ParsePersonError::ParseInt`.
impl FromStr for Person {
type Err = ParsePersonError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 0 {
return Err(ParsePersonError::BadLen);
}
let mut it = s.split(',');
let Some(name) = it.next() else {
return Err(ParsePersonError::BadLen);
};
if name.is_empty() {
return Err(ParsePersonError::NoName);
};
let Some(age) = it.next() else {
return Err(ParsePersonError::BadLen);
};
let age = age.parse::<u8>().map_err(ParsePersonError::ParseInt)?;
if let Some(_too_many_commas) = it.next() {
return Err(ParsePersonError::BadLen);
}
Ok(Person {
name: name.to_string(),
age,
})
}
}
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 In Rust
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);
}