A Rust crate with both `main.rs` and `lib.rs` performing primality checking


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):

  1. The fact that I have both main.rs and lib.rs. Cargo couldn’t care less apparently. Because it executes all tests (even the documentation test) and also executes as a command-line program when cargo run is called (both with and without arguments).
  2. The general quality of the code. I feel it can always be improved and made more Rusty.
  3. The organization of the files and the containing code, and the documentation.
  4. The external tests that I’ve included. Any additions/modifications necessary?

I reiterate my invitation for general criticism as well.