#[derive(Debug, PartialEq)] pub struct EmptyListError; // implementing Error for my error to satisfy Box // not needed ATM // impl std::error::Error for EmptyListError {} // so we can pass EmptyListError to println! impl std::fmt::Display for EmptyListError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Error: list must not be empty") } } // Result<...,Box> is overkill // this is intuitive enough for caller error detection // actually probably should use Option instead, but this is better for extension pub fn min_and_max(list: &[u32]) -> Result<(u32, u32), EmptyListError> { if list.is_empty() { return Err(EmptyListError); } let mut min:u32 = 100; let mut max:u32 = 0; for i in list { if *i > max { max = *i; } if *i < min { min = *i; } } Ok((min,max)) } #[cfg(test)] mod tests { use super::*; // need access to functions above #[test] fn test_arrays() { // empty array let empty = []; let empty_min_and_max = min_and_max(&empty); assert!(empty_min_and_max.as_ref().is_err()); // as_ref() so we don't perform move assert!(*(empty_min_and_max.as_ref().err().unwrap()) == EmptyListError); println!("Expected: {}", *(empty_min_and_max.as_ref().err().unwrap())); // array - contiguous, entirely on the stack, fixed at compile time, no pointer indirection let l = [ 1, 2, 10, 100, 50, 75, 36, 21]; let l_min_and_max = min_and_max(&l); assert!(l_min_and_max.as_ref().unwrap().0 == 1); assert!(l_min_and_max.as_ref().unwrap().1 == 100); // vec - stack: ptr, len, cap; ptr -> heap: contiguous allocation of capacity // when variable goes out of scope drop() is called to deallocate the heap let v = vec![1,2,3]; let v_min_and_max = min_and_max(&v); assert!(v_min_and_max.as_ref().unwrap().0 == 1); assert!(v_min_and_max.as_ref().unwrap().1 == 3); let s = String::from("hello"); let r = &s; println!("{}", *r); // works because there is no implicit move // let thing = *r; // doesn't work because String does not implement Copy trait } }