Snippets
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(())
}
// The output is wrapped in a Result to allow matching on errors
// Returns an Iterator to the Reader of the lines of the file.
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 {
/// Writes the file replacing if it already exists
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 {
/// Returns the result of set_extension with the appended extension
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
# Cargo.toml
[dev-dependencies]
assert_cmd = "2.0.7"
predicates = "2.1.5"
// main.rs
use std::io::BufRead;
fn main() {
for line in std::io::stdin().lock().lines() {
println!("{}", line.expect("Unable to read input line"));
}
}
// tests/integration_test.rs
use assert_cmd::prelude::*; // Add methods on commands
use predicates::prelude::*; // Used for writing assertions
use std::{
fs::{self, File},
process::Command,
};
#[test]
fn run_executable() -> Result<(), Box<dyn std::error::Error>> {
// You may want to include the calls to trim but depending on your use case you might not.
// They are optional but included as they are easier to remove than figure out where to add
// Note that input it not trimmed, only expected_output and output
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() // Trim expected output
.to_owned();
Command::cargo_bin("myapp")?
.stdin(input_file)
.assert()
.stdout(
predicate::str::diff(expected_output).trim(), // Trims actual output
);
Ok(())
}
Get input arguments to a program π
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");
// This would break only the inner loop
//break;
// This breaks the outer 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)]
/// Wraps a float and implements Ord but panics if value is NaN
struct FloatNonNAN(f64);
impl FloatNonNAN {
/// Creates a new [Self] with constraint that value cannot be NAN or function panics
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 π
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); // Might want to log this error or return a result as suitable to your application
None
}
}
}