Introduction
I decided to get my feet wet in Rust by going ahead an implementing a full crate with the tests, documentation, and all other accompanying stuff. This is a toy implementation of a library containing a function that checks whether a given u64
is prime or not.
What I've also done is implement a main.rs
in addition to the lib.rs
, that produces an executable when the cargo run
or cargo build
command is issued. So far the toolchain hasn't complained yet. It seems to be working fine, but I seek adivce on the practicality of it.
The organization
Here is the full structure (excluding the target
folder):
prime
+- src
| +- lib.rs
| +- main.rs
| +- prime.rs
+- tests
| +- extern_test.rs
+- Cargo.lock
+- Cargo.toml
The files
lib.rs
pub mod prime;
main.rs
pub mod prime;
use std::env;
use std::io::stdin;
fn take_input() {
println!("Prime cheker utility.\n=====================\n");
loop {
process_single_line();
if user_wants_to_exit() {
break;
}
}
}
fn process_single_line() {
let mut num_str: String = String::new();
println!("Enter the number to check : ");
stdin().read_line(&mut num_str).unwrap();
process_string(num_str.trim());
}
fn user_wants_to_exit() -> bool {
let mut usr_str = String::new();
println!("Do you want to exit? (y/n) : ");
stdin()
.read_line(&mut usr_str)
.expect("Error while reading input.");
let trimmed = usr_str.trim();
trimmed == "y" || trimmed == "Y" || trimmed.to_lowercase() == "yes"
}
fn process_string(num_str: &str) {
let num = num_str.parse::<u64>().expect(INVALID_NUMBER);
println!(
"The integer {} is{} a prime.",
num,
match prime::is_prime(num) {
true => "",
false => " not",
}
);
}
const HELP_TEXT: &str = "USAGE:\n\n1. prime\n2. prime [unsigned integer]\n";
const INVALID_NUMBER: &str = "Please enter a valid unsigned integer.";
fn main() {
let args: Vec<String> = env::args().collect();
match args.len() {
1 => take_input(),
2 => process_string(args[1].trim()),
_ => {
println!("{}", HELP_TEXT);
}
}
}
prime.rs
/// This function takes a 64-bit unsigned integer and checks if it is a prime.
///
/// If the number is prime, `true` is returned, and vice-versa.
///
/// #Example
///
/// ```rust
/// use prime_util::*;
///
/// let result = prime::is_prime(31);
/// assert_eq!(result, true);
/// ```
pub fn is_prime(num: u64) -> bool {
if num < 2 {
return false;
}
if num == 2 || num == 3 {
return true;
}
// Even numbers and multiples of 3 are eliminated
if num % 2 == 0 || num % 3 == 0 {
return false;
}
// Optimized divisor approach
// First we calculate the maximum limit of iteration
let limit = (num as f64).sqrt() as u64;
// We start the iteration from 5 (2 and 3 have been already tested)
let mut divisor = 5;
// The step alternates between 2 and 4 to keep the divisor of the form
// 6k +/- 1, where k is an integer
let mut step = 2;
while divisor <= limit {
if num % divisor == 0 {
return false;
}
divisor += step;
step = if step == 2 { 4 } else { 2 }
}
true
}
extern_test.rs
extern crate prime_util;
#[cfg(test)]
mod integration_test {
use prime_util::prime;
#[test]
fn small_number_checks() {
assert_eq!(prime::is_prime(11), true);
assert_eq!(prime::is_prime(12), false);
assert_eq!(prime::is_prime(13), true);
}
#[test]
fn large_number_checks() {
assert_eq!(prime::is_prime(179434027), true);
assert_eq!(prime::is_prime(179434029), false);
assert_eq!(prime::is_prime(179434031), false);
assert_eq!(prime::is_prime(179434033), true);
}
#[test]
fn first_10_numbers_tested() {
assert_eq!(prime::is_prime(0), false);
assert_eq!(prime::is_prime(1), false);
assert_eq!(prime::is_prime(2), true);
assert_eq!(prime::is_prime(3), true);
assert_eq!(prime::is_prime(4), false);
assert_eq!(prime::is_prime(5), true);
assert_eq!(prime::is_prime(6), false);
assert_eq!(prime::is_prime(7), true);
assert_eq!(prime::is_prime(8), false);
assert_eq!(prime::is_prime(9), false);
assert_eq!(prime::is_prime(10), false);
}
}
Others
Cargo.toml is the default one. Nothing has been changed.
Topics of interest
I would like to point out some specific areas that I would like to hear advice on (although I always welcome comments on any aspect of the project):
- The fact that I have both
main.rs
andlib.rs
. Cargo couldn't care less apparently. Because it executes all tests (even the documentation test) and also executes as a command-line program whencargo run
is called (both with and without arguments). - The general quality of the code. I feel it can always be improved and made more Rusty.
- The organization of the files and the containing code, and the documentation.
- The external tests that I've included. Any additions/modifications necessary?
I reiterate my invitation for general criticism as well.