Monday, June 26, 2023

Grep sample in Rust

 



Following the example here, I've created a sample & simple grep application in rust.


main.rs

use std::{env, process};

fn main() {
let args: Vec<String> = env::args().collect();
let config = rust1::Config::new(&args)
.unwrap_or_else(|err| {
eprintln!("parsing arguments failed: {err}");
process::exit(1);
});

if let Err(e) = rust1::run(config) {
eprintln!("application error {}", e);
process::exit(1);
}
}


lib.rs

use std::{env, fs};
use std::error::Error;

pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}

impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() != 3 {
return Err("invalid arguments amount");
}
let query = args[1].clone();
let file_path = args[2].clone();

let ignore_case = env::var("IGNORE_CASE").is_ok();

Ok(Config { query, file_path, ignore_case })
}
}


pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)
.expect("should be able to read the file");

let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};

for line in results {
println!("{line}")
}
Ok(())
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();

for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}

results
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();

for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}

results
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";

assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}

#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

assert_eq!(vec!["Rust:", "Trust me."], search_case_insensitive(query, contents));
}
}



Saturday, June 10, 2023

Testing in Rust


 


In this post we show an example of testing in rust.



use std::{thread, time};
fn main() {}

// this is our code
mod toys {
use std::{thread, time};

pub struct Toy<'a> {
name: &'a str,
size: u32,
move_time: u64,
}

impl<'a> Toy<'a> {
pub const fn new(name: &'a str, size: u32) -> Self {
if name.len() == 0 {
panic!("name must be supplied")
}
Self {
name,
size,
move_time: (size / 2) as u64,
}
}
pub fn is_bigger(&self, other: Toy) -> bool {
self.size > other.size
}

pub fn how_long_to_get_over_here(&self) -> u64 {
println!("Come here {}", self.name);
thread::sleep(time::Duration::from_secs(self.move_time));
self.move_time
}
}


pub const DINO: Toy = Toy::new("Rex", 10);
pub const CAR: Toy = Toy::new("Mustang", 4);
}


/*
here we add a new module with config `test`.
this means the code will not be included in our final module.
*/
#[cfg(test)]
mod tests {
// as the tested module is in different scope, we need to explicitly include it
use super::toys;

// test functions are annotated with `test`
#[test]
fn size_matters() {
// we can use assert macro to check results
assert!(toys::DINO.is_bigger(toys::CAR));
}

/*
we can mark test functions not to run by default.
this is required, for example, in case the test runs for a long period
*/
#[ignore]
#[test]
fn calling() {
let seconds = toys::CAR.how_long_to_get_over_here();
/*
1. we use assert_eq here. we can also use assert_ne
2. we also add description the the asser macro. this is displayed in case the assert fails
*/

assert_eq!(1, seconds, "should arrive very quickly");
}

// here we add the should_panic annotation which check for an expected panic
#[test]
#[should_panic]
fn must_use_name() {
toys::Toy::new("", 0);
}
}


Now we run the tests:

$ cargo test
Compiling rust1 v0.1.0 (/home/alon/git/rust1)
warning: unused imports: `thread`, `time`
--> src/main.rs:1:11
|
1 | use std::{thread, time};
| ^^^^^^ ^^^^
|
= note: `#[warn(unused_imports)]` on by default

warning: `rust1` (bin "rust1" test) generated 1 warning (run `cargo fix --bin "rust1" --tests` to apply 1 suggestion)
Finished test [unoptimized + debuginfo] target(s) in 0.26s
Running unittests src/main.rs (target/debug/deps/rust1-6a33c00b5734b10f)

running 3 tests
test tests::calling ... ignored
test tests::size_matters ... ok
test tests::must_use_name - should panic ... ok

test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s


Notice the ignored test are not run. To run the ignore test, we should explicitly ask:

$ cargo test -- --ignored
warning: unused imports: `thread`, `time`
--> src/main.rs:1:11
|
1 | use std::{thread, time};
| ^^^^^^ ^^^^
|
= note: `#[warn(unused_imports)]` on by default

warning: `rust1` (bin "rust1" test) generated 1 warning (run `cargo fix --bin "rust1" --tests` to apply 1 suggestion)
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/main.rs (target/debug/deps/rust1-6a33c00b5734b10f)

running 1 test
test tests::calling ... FAILED

failures:

---- tests::calling stdout ----
Come here Mustang
thread 'tests::calling' panicked at 'assertion failed: `(left == right)`
left: `1`,
right: `2`: should arrive very quickly', src/main.rs:71:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
tests::calling

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 2 filtered out; finished in 2.00s

error: test failed, to rerun pass `--bin rust1`







Monday, June 5, 2023

Rust Generics and Traits


 


This post includes an example demonstrating usage of Generics and Traits in Rust.


use std::fmt::Display;

fn main() {
// trait is similar to interfaces in other languages
trait Hashed {
fn get_hash_key(&self) -> String;
}

// here we have a trait that also includes a default implementation
trait FirstAndLastName {
fn get_names(&self) -> (&str, &str);
fn get_names_last_before_first(&self) -> (&str, &str) {
let (name1, name2) = &self.get_names();
(name2, name1)
}
}

// this is a generic struct, it uses the 'Display' bound to make sure the the T is printable
struct SomethingWithNames<T:Display> {
something: T,
first_name: String,
last_name: String,
}

// here we create an implementation for the generic struct. the return value is the generic type
impl<T:Display> SomethingWithNames<T> {
fn get_value(&self) -> &T {
&self.something
}
}

// this is a function with generic implementation
fn print_something<T: Display>(something: SomethingWithNames<T>) {
let (name1, name2) = something.get_names();
println!("The mystery name of {} is: {}, {}\nThe hash is {}",
something.get_value(), name1, name2, something.get_hash_key());
}

// we implement the hash trait for our first struct
impl<T: Display> Hashed for SomethingWithNames<T> {
fn get_hash_key(&self) -> String {
format!("{}{}{}", &self.something, &self.first_name, &self.last_name)
}
}

// and we implement the hash trait for our second struct
impl Hashed for MyNumber {
fn get_hash_key(&self) -> String {
format!("{}", &self.n)
}
}

// we also implement the names trait for the generic struct
impl<T:Display> FirstAndLastName for SomethingWithNames<T> {
fn get_names(&self) -> (&str, &str) {
(&self.first_name, &self.last_name)
}
}

// here we create 2 different "somethings", based on different types: int and string
let the_answer = SomethingWithNames {
something: 42,
first_name: String::from("the answer"),
last_name: String::from("to everything"),
};

let john = SomethingWithNames {
something: "john",
first_name: String::from("john"),
last_name: String::from("doe"),
};

print_something(the_answer);
print_something(john);


// this is another totally different struct that gets the hash implementation
struct MyNumber {
n: i32,
}

let just_a_number = MyNumber {
n: 13,
};

println!("The hash for just a number is {}", just_a_number.get_hash_key());
}