In just a few steps, we will be working with different modules:
- First, we are going to implement the rust-pilib crate. As a simple example, it estimates the constant pi using the Monte Carlo method. This method is somewhat similar to throwing darts at a dartboard and counting the hits. Read more on Wikipedia (https://en.wikipedia.org/wiki/Monte_Carlo_method). Add to the tests submodule this snippet:
use rand::prelude::*;
pub fn monte_carlo_pi(iterations: usize) -> f32 {
let mut inside_circle = 0;
for _ in 0..iterations {
// generate two random coordinates between 0 and 1
let x: f32 = random::<f32>();
let y: f32 = random::<f32>();
// calculate the circular distance from 0, 0
if x.powi(2) + y.powi(2) <= 1_f32 {
// if it's within the circle, increase the count
inside_circle += 1;
}
}
// return the ratio of 4 times the hits to the total
iterations
(4_f32 * inside_circle as f32) / iterations as f32
}
- Additionally, the Monte Carlo method uses a random number generator. Since Rust doesn't come with one in its standard library, an external crate is required! Modify Cargo.toml of the rust-pilib project to add the dependency:
[dependencies]
rand = "^0.5"
- As good engineers, we are also going to add tests to our new library. Replace the original test module with the following tests to approximate pi using the Monte Carlo method:
#[cfg(test)]
mod tests {
// import the parent crate's functions
use super::*;
fn is_reasonably_pi(pi: f32) -> bool {
pi >= 3_f32 && pi <= 4.5_f32
}
#[test]
fn test_monte_carlo_pi_1() {
let pi = monte_carlo_pi(1);
assert!(pi == 0_f32 || pi == 4_f32);
}
#[test]
fn test_monte_carlo_pi_500() {
let pi = monte_carlo_pi(500);
assert!(is_reasonably_pi(pi));
}
We can even go beyond 500 iterations:
#[test]
fn test_monte_carlo_pi_1000() {
let pi = monte_carlo_pi(1000);
assert!(is_reasonably_pi(pi));
}
#[test]
fn test_monte_carlo_pi_5000() {
let pi = monte_carlo_pi(5000);
assert!(is_reasonably_pi(pi));
}
}
- Next, let's run the tests so we are certain of the quality of our product. Run cargo test in the root of the rust-pilib project. The output should be somewhat like this:
$ cargo test
Compiling libc v0.2.50
Compiling rand_core v0.4.0
Compiling rand_core v0.3.1
Compiling rand v0.5.6
Compiling rust-pilib v0.1.0 (Rust-Cookbook/Chapter01/rust-pilib)
Finished dev [unoptimized + debuginfo] target(s) in 3.78s
Running target/debug/deps/rust_pilib-d47d917c08b39638
running 4 tests
test tests::test_monte_carlo_pi_1 ... ok
test tests::test_monte_carlo_pi_500 ... ok
test tests::test_monte_carlo_pi_1000 ... ok
test tests::test_monte_carlo_pi_5000 ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests rust-pilib
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- Now we want to offer the crate's feature(s) to the user, which is why we created a second project for the user to execute. Here, we declare to use the other library as an external crate first. Add the following to Cargo.toml in the pi-estimator project:
[dependencies]
rust-pilib = { path = '../rust-pilib', version = '*'}
- Then, let's take a look at the src/main.rs file. Rust looks there to find a main function to run and, by default, it simply prints Hello, World! to standard output. Let's replace that with a function call:
// import from the module above
use printer::pretty_print_pi_approx;
fn main() {
pretty_print_pi_approx(100_000);
}
- Now, where does this new function live? It has its own module:
// Rust will also accept if you implement it right away
mod printer {
// import a function from an external crate (no more extern
declaration required!)
use rust_pilib::monte_carlo_pi;
// internal crates can always be imported using the crate
// prefix
use crate::rounding::round;
pub fn pretty_print_pi_approx(iterations: usize) {
let pi = monte_carlo_pi(iterations);
let places: usize = 2;
println!("Pi is ~ {} and rounded to {} places {}", pi,
places, round(pi, places));
}
}
- This module was implemented inline, which is common for tests—but works almost like it was its own file. Looking at the use statements, we are still missing a module, however: rounding. Create a file in the same directory as main.rs and name it rounding.rs. Add this public function and its test to the file:
pub fn round(nr: f32, places: usize) -> f32 {
let multiplier = 10_f32.powi(places as i32);
(nr * multiplier + 0.5).floor() / multiplier
}
#[cfg(test)]
mod tests {
use super::round;
#[test]
fn round_positive() {
assert_eq!(round(3.123456, 2), 3.12);
assert_eq!(round(3.123456, 4), 3.1235);
assert_eq!(round(3.999999, 2), 4.0);
assert_eq!(round(3.0, 2), 3.0);
assert_eq!(round(9.99999, 2), 10.0);
assert_eq!(round(0_f32, 2), 0_f32);
}
#[test]
fn round_negative() {
assert_eq!(round(-3.123456, 2), -3.12);
assert_eq!(round(-3.123456, 4), -3.1235);
assert_eq!(round(-3.999999, 2), -4.0);
assert_eq!(round(-3.0, 2), -3.0);
assert_eq!(round(-9.99999, 2), -10.0);
}
}
- So far, the module is ignored by the compiler since it was never declared. Let's do just that and add two lines at the top of main.rs:
// declare the module by its file name
mod rounding;
- Lastly, we want to see whether everything worked. cd into the root directory of the pi-estimator project and run cargo run. The output should look similar to this (note that the library crate and dependencies are actually built with pi-estimator):
$ cargo run
Compiling libc v0.2.50
Compiling rand_core v0.4.0
Compiling rand_core v0.3.1
Compiling rand v0.5.6
Compiling rust-pilib v0.1.0 (Rust-Cookbook/Chapter01/rust-pilib)
Compiling pi-estimator v0.1.0 (Rust-Cookbook/Chapter01/pi-
estimator)
Finished dev [unoptimized + debuginfo] target(s) in 4.17s
Running `target/debug/pi-estimator`
Pi is ~ 3.13848 and rounded to 2 places 3.14
- Library crates are not the only ones to have tests. Run cargo test to execute the tests in the new pi-estimator project:
$ cargo test
Compiling pi-estimator v0.1.0 (Rust-Cookbook/Chapter01/pi-
estimator)
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
Running target/debug/deps/pi_estimator-1c0d8d523fadde02
running 2 tests
test rounding::tests::round_negative ... ok
test rounding::tests::round_positive ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Now, let's go behind the scenes to understand the code better.