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 test
will 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
- "
Point
doesn'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 == a
should always be truea == b
meansb == a
a == b
andb == c
meansa == 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
PartialEq
andEq
, but they also allow other comparisons (<
,<=
,>=
,>
). - Generally, everything is
Ord
, exceptf32
andf64
. - 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
PartialOrd
orOrd
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.
Debug
is for debug printing- can be derived
Display
is 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.
Eq
andPartialOrd
both requirePartialEq
.Ord
requires bothEq
andPartialOrd
#[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 forHashMap
Default
- a type gets adefault()
method to produce a default value0
is used for numbers,""
for strings- collections starts as empty
Option
fields will beNone
Clone
adds 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