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 assertions
    • assert_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 type Point"
    • can't compare two Points
  • "Point doesn't implement Debug"
    • 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:

  1. a == a should always be true
  2. a == b means b == a
  3. a == b and b == c means a == 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 and Eq, but they also allow other comparisons (<, <=, >=, >).
  • Generally, everything is Ord, except f32 and f64.
  • 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 or Ord

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 and PartialOrd both require PartialEq.
  • Ord requires both Eq and PartialOrd
#[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 for HashMap
  • Default - a type gets a default() method to produce a default value
    • 0 is used for numbers, "" for strings
    • collections starts as empty
    • Option fields will be None
  • Clone adds a clone() method to produce a deep copy of a value

derive lists can get be pretty long.

Documentation

Formatting and Linting

rustfmt is a default Rust formatter

cargo fmt

Clippy is a linter for Rust code

cargo clippy