Good Design Practices
Two types of Rust crates
- binary - a program you can run directly
- library - a collection of useful code that you can re-use in a binary
Binary crate
cargo new my_app
my_app/
├── src/
│ └── main.rs
└── Cargo.toml
Library crate
cargo new --lib my_library
my_library/
├── src/
│ └── lib.rs
└── Cargo.toml
How to run the code in a library?
Use tests!
#![allow(unused)] fn main() { pub fn add(left: usize, right: usize) -> usize { left + right } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } } }
Testing
- mark your function with
#[test] - use
assert!,assert_eq!,assert_ne!for assertionsassert_eq!,assert_ne!will show you the difference between left and right arguments- all assertions take an optional custom error message argument
- first failed assertion in a test function will stop the current test, other tests will still run
cargo testwill run all tests
Assertions for your own types:
struct Point(i32, i32);
fn main() {
let p = Point (1, 2);
assert_eq!(p, Point(1, 2));
}
Errors:
- "binary operation
==cannot be applied to typePoint"- can't compare two Points
- "
Pointdoesn't implementDebug"- can't print out a Point in error messages
Derives - adding behavior to your types
#[derive(Debug, PartialEq)] struct Point(i32, i32); fn main() { let p = Point (1, 2); assert_eq!(p, Point(1, 2)); }
Debug
Allows printing of values with debug formatting
#[derive(Debug)] struct Point { x: i32, y: i32 } #[derive(Debug)] struct TuplePoint(i32, i32); fn main() { let p = Point { y: 2, x: 1 }; let tp = TuplePoint (1, 2); println!("{:?}", p); // Point { x: 1, y: 2 } println!("{:?}", tp); // TuplePoint (1, 2) }
PartialEq
- Allows checking for equality (
==and!=) - For complex types does a field-by-field comparison
- For references it compares data that references observe
- Can compare arrays and slices if their elements are
PartialEq, too
PartialEq and Eq
Eq means strict mathematical equality:
a == ashould always be truea == bmeansb == aa == bandb == cmeansa == c
IEEE 754 floating point numbers (f32 and f64) break the first rule (NaN == NaN is always false). They are PartialEq and not Eq.
PartialOrd and Ord
- Same as
PartialEqandEq, but they also allow other comparisons (<,<=,>=,>). - Generally, everything is
Ord, exceptf32andf64. - Characters are compared by their code point numerical values
- Arrays and slices are compared element by element. Length acts as a tiebreaker.
"aaa" < "b", but"aaa" > "a"- elements themselves have to be
PartialOrdorOrd
How derives work?
Debug,PartialEq,Eq, etc. are simultaneously names of "Traits" and names of "derive macros".- If a trait has a corresponding derive macro it can be "derived":
- Rust will generate a default implementation.
- Not all traits have a corresponding derive macros
- these traits have to be implemented manually.
Debug and Display
- a pair of traits.
Debugis for debug printing- can be derived
Displayis for user-facing printing- cannot be derived, and must be implemented manually
println!("{:?}", value); // uses `Debug`
println!("{:#?}", value); // uses `Debug` and pretty-prints structures
println!("{}", value); // uses `Display`
Traits dependencies
Traits can depend on each other.
EqandPartialOrdboth requirePartialEq.Ordrequires bothEqandPartialOrd
#[derive(Debug, Ord)] // will give an error
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] // Ok
Other useful traits:
Hash- a type can be used as a key forHashMapDefault- a type gets adefault()method to produce a default value0is used for numbers,""for strings- collections starts as empty
Optionfields will beNone
Cloneadds aclone()method to produce a deep copy of a value
derive lists can get be pretty long.
Documentation
///marks doc comments- Markdown
- Rust fragments in doc comments produce documentation tests
- Use it to test you examples.
- Example from a standard library:
Formatting and Linting
rustfmt is a default Rust formatter
cargo fmt
Clippy is a linter for Rust code
cargo clippy