Chester Wyke September 02, 2023 Updated: April 15, 2025 #rust
Library Candidates
Read number from stdin
Source: https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#handling-invalid-input
use std::io;
fn main() {
println!("Please input a number");
let mut user_input = String::new();
io::stdin()
.read_line(&mut user_input)
.expect("Failed to read line");
if let Ok(num) = user_input.trim().parse::<u32>() {
println!("The number entered is {num}");
}
}
Read file (line by line)
Source: https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html
use std::error::Error;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
fn main() -> Result<(), Box<dyn Error>> {
for line in read_lines("filename")? {
let line = line?;
println!("{line}");
}
Ok(())
}
fn read_lines<P: AsRef<Path>>(path: P) -> io::Result<io::Lines<io::BufReader<File>>>
{
let file = File::open(path)?;
Ok(io::BufReader::new(file).lines())
}
Read file to string
fn main() -> Result<(), Box<dyn Error>> {
let _ = fs::read_to_string("filename")?;
Ok(())
}
Write string to a file
use std::{error::Error, io::Write, path::Path};
fn main() -> Result<(), Box<dyn Error>> {
write_to_path("filename.txt", "text for file")?;
Ok(())
}
fn write_to_path<P: AsRef<Path>>(path: P, s: &str) -> Result<(), Box<dyn Error>> {
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)?;
file.write_all(s.as_bytes())?;
Ok(())
}
OR with a trait (may not be great for a library as is because it’s very opinionated with regard to overwrite)
use anyhow::Context as _;
pub trait SaveToFile {
fn write_to_path<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()>;
}
impl<T: AsRef<str>> SaveToFile for T {
fn write_to_path<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let mut file = std::fs::OpenOptions::new()
.truncate(true)
.write(true)
.create(true)
.open(&path)
.with_context(|| {
format!(
"failed to open file for writing. Filename: {:?}",
path.as_ref()
)
})?;
file.write_all(self.as_ref().as_bytes())
.with_context(|| format!("failed to write contents to: {:?}", path.as_ref()))?;
Ok(())
}
}
Print without new line (and flush)
Note: flush is required as print!()
does not automatically flush like println!()
does
print!("This may not print when we want unless we flush");
io::stdout().flush()?;
Add an extension to a path
Source: https://users.rust-lang.org/t/append-an-additional-extension/23586/13
pub trait PathExtension {
fn add_extension<P: AsRef<Path>>(&mut self, new_extension: P) -> bool;
}
impl PathExtension for PathBuf {
fn add_extension<P: AsRef<Path>>(&mut self, extension: P) -> bool {
match self.extension() {
Some(ext) => {
let mut ext = ext.to_os_string();
ext.push(".");
ext.push(extension.as_ref());
self.set_extension(ext)
}
None => self.set_extension(extension.as_ref()),
}
}
}
Add commas to number for display
NB: To the best of my recollection here is also a crate that provides a more robust implementation
fn as_string_with_separators(value: usize) -> String {
value
.to_string()
.as_bytes()
.rchunks(3)
.rev()
.map(std::str::from_utf8)
.collect::<Result<Vec<&str>, _>>()
.unwrap()
.join(",")
}
Replace escaped special characters with the actual special characters
This one feels like there should be a better way but I haven’t needed it so not sure but the aho_corasick crate could do multiple find and replace simultaneously instead of sequentially if I recall correctly.
fn clean_msg<S: AsRef<str>>(msg: S) -> String {
msg.as_ref().replace(r"\n", "\n").replace(r#"\""#, "\"")
}
Tests
Unit tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
Panic expectation
#[should_panic(expected = "message seems to need to be on first line of text")]
fn it_should_panic() {}
Run CLI App to test it
NOTE: Only works from integration tests
[dev-dependencies]
assert_cmd = "2.0.7"
predicates = "2.1.5"
use std::io::BufRead;
fn main() {
for line in std::io::stdin().lock().lines() {
println!("{}", line.expect("Unable to read input line"));
}
}
use assert_cmd::prelude::*; use predicates::prelude::*; use std::{
fs::{self, File},
process::Command,
};
#[test]
fn run_executable() -> Result<(), Box<dyn std::error::Error>> {
let input_file = File::open("input.txt")?;
let expected_output_filename = "expected_output.txt";
let expected_output = fs::read_to_string(expected_output_filename)
.map_err(|e| format!("Failed to load output: {expected_output_filename} Error:{e}"))?
.trim() .to_owned();
Command::cargo_bin("myapp")?
.stdin(input_file)
.assert()
.stdout(
predicate::str::diff(expected_output).trim(), );
Ok(())
}
Source: https://doc.rust-lang.org/book/ch12-01-accepting-command-line-arguments.html#saving-the-argument-values-in-variables
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let query = &args[1];
let file_path = &args[2];
println!("Searching for {}", query);
println!("In file {}", file_path);
}
Loop nesting and labels
Source: https://doc.rust-lang.org/rust-by-example/flow_control/loop/nested.html
fn main() {
'outer: loop {
println!("Entered the outer loop");
'inner: loop {
println!("Entered the inner loop");
break 'outer;
}
println!("This point will never be reached");
}
println!("Exited the outer loop");
}
Float Eq and Ord Wrapper
This may be better served by using the crate float_ord. See useful crate
#[derive(Debug, Clone, Copy, PartialEq)]
struct FloatNonNAN(f64);
impl FloatNonNAN {
pub fn new(value: f64) -> Self {
assert!(
!value.is_nan(),
"NAN is not allowed as it is not equal to itself and thus invalid for an impl of Ord"
);
Self(value)
}
}
impl Eq for FloatNonNAN {}
impl Ord for FloatNonNAN {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0
.partial_cmp(&other.0)
.expect("This should work unless one of them is NAN")
}
}
impl PartialOrd for FloatNonNAN {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl AsRef<f64> for FloatNonNAN {
fn as_ref(&self) -> &f64 {
&self.0
}
}
impl PartialEq<f64> for FloatNonNAN {
fn eq(&self, other: &f64) -> bool {
&self.0 == other
}
}
impl From<FloatNonNAN> for f64 {
fn from(value: FloatNonNAN) -> Self {
value.0
}
}
impl std::ops::Mul for FloatNonNAN {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
FloatNonNAN::new(self.0 * rhs.0)
}
}
Collect into a result
One fail means the first error is returned (Remember this from the rust book but couldn’t find it).
Mostly needed when I’m using iterators and need to do a map step that may fail.
Usual issue is not remembering to wrap the good option in Ok.
fn main() -> Result<(), Box<dyn std::error::Error>> {
let numbers: Vec<u8> = (0..10).step_by(2).collect();
let all_even: Vec<u8> = numbers
.iter()
.cloned()
.map(|x| {
if x % 2 == 0 {
Ok(x)
} else {
Err("Oops we founds a odd number")
}
})
.collect::<Result<Vec<u8>, _>>()?;
println!("{all_even:?}");
Ok(())
}
Get number of seconds until midnight
Source: https://stackoverflow.com/questions/47708305/calculate-the-duration-between-now-and-the-next-midnight-using-chrono
Requires the chrono crate.
fn main() {
let duration = seconds_to(1, 0, 0, 0).unwrap();
println!("Duration until midnight {duration:?}");
}
fn seconds_to(days: i64, hour: u32, min: u32, sec: u32) -> Option<std::time::Duration> {
let now = chrono::Local::now();
let new_date_time = (now + chrono::Duration::days(days))
.date_naive()
.and_hms_opt(hour, min, sec)?;
match new_date_time
.signed_duration_since(now.naive_local())
.to_std()
{
Ok(result) => Some(result),
Err(e) => {
dbg!(e); None
}
}
}
Random
Source: https://rust-random.github.io/book/guide-values.html#the-rng-trait
Copied here for faster lookup without having to search for it again.
cargo add rand
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
let i: i32 = rng.gen();
println!("i = {i}");
let x: f64 = rng.gen();
println!("x = {x}");
println!("roll = {}", rng.gen_range(1..=6));
}
Source: https://stackoverflow.com/questions/32289605/how-do-i-write-a-wrapper-for-a-macro-without-repeating-the-rules
Useful when you want provide the same calling syntax as format!()
but you want to perform additional steps.
In this example postfix the text with error and add the position it occurred in the code.
#[macro_export]
macro_rules! internal_error {
($($arg:tt)*) => {{
let res = format!($($arg)*);
let internal_error_msg = format!(
"{}\ninternal error: {}:{}:{}",
res,
file!(),
line!(),
column!()
);
tracing::error!(?internal_error_msg);
internal_error_msg
}};
}