In just a few steps, we will be working with different modules:
First, we are going to implement therust-pilibcrate. 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 thetestssubmodule 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:
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.