Rust Bites Code
          
          
            Rust Bite - Hacks & Helpers
            
              ironing out wrinkles in tricky code - tricky, of course, is in the eye of the beholder
            
          
        
        
        
          This Bite presents techniques for solving code problems that I encountered or which
          appear often in QA forums.  Of course there are many of those.  I've selected ones that I
          thought were interesting.
        
        
          I expect to add to this page continuously as I work on Rust-based projects.
        
        
        1.0  Struct with method hosting thread
        
          Asynchronous processing often needs to share some mutable data with the thread host.
          For a method, that usually means sharing member data of a host object.  But that needs to
          be constructed carefully so that:
          
            - 
              Both read and write access needs to be serialized to satisfy the Rust ownership
              policies.  We do that with a Mutex.
            
 
            - 
              The construction needs to guarantee that the thread's reference won't
              become invalid if the thread outlives the host object.  We do that by using an
              Arc, a thread-safe reference-counted pointer.  The Arc stores its content
              on the heap, returning a reference to that.  Any other code that needs access
              to the Arc's data calls Arc::clone(&original).  Now, the content will live
              until every reference to it has been dropped.
            
 
          
        
        
          Compiler error messages give a lot of help: lifetime errors are usually problems
          handling the Arc correctly. Move errors are often attempting to use a value instead
          of a reference in the thread.
        
        
          You will find code for all examples below in
          
            RustBiteByByte/hacks_and_helps/methods_with_threads
          
        
        
        
        1.1 - User-defined Type with thread start() method
        
          This example is a simple demo that sums a loop index in its thread closure.  This
          same technique was used in the RustComm library
          that does something much more useful; it uses message passing to communicate between
          two endpoints using std::net::TcpStream and std::net::TcpListener.
        
        
          
            
              Test1 - User-defined type with thread start()
              
use std::thread::{JoinHandle};
use std::sync::{Arc, Mutex};
struct Test1 {
  count: Arc<Mutex<i32>>,
}
impl Test1 {
  fn new() -> Test1 {
    Test1 {
      count: Arc::new(Mutex::new(0)),
    }
  }
  fn start(&mut self) -> JoinHandle<()> {
    /* get a shared pointer */
    let local_count = self.count.clone();
    std::thread::spawn(move || {
      for i in 0..5 {
        let mut data = local_count.lock().unwrap();
        *data += i;
        print!("\n  {:?}", data);
      }
    })
  }
  fn show_count(&self) {
    print!(
      "\n\n  t1 result = {:?}",
      self.count.lock().unwrap()
    );
  }
}
              
            
            
              Using Code:
              
fn main() {
  print!("\n  -- demo Test1 --");
  let mut t1 = Test1::new();
  let handle = t1.start();
  /* do some useful work here */
  let _ = handle.join();
  t1.show_count();
  println!();
}
              
              Output:
              
-- demo Test1 --
0
1
3
6
10
t1 result = 10
              
            
          
        
        
        
          
          
          1.2  User-defined Type with thread starting in constructor new()
        
        
          The code below illustrates how to start a thread in the type's
          constructor, new().  That would be appropriate if an instance's functionality
          depended on the thread running.
        
        
          
            
              Test3 - thread starting in new()
              
struct Test3 {
  counter: Arc<Mutex<i32>>,
}
impl Test3 {
  /*-----------------------------------------------------
    This example uses the same technique as Test1, but
    moves the thread into new(), the Test3 constructor.
    That would be a good idea for any type whose instances
    need the running thread to operate as expected.
    Note that new() now returns a tuple with the newly
    constructed Test2 instance and the thread handle.
    Look at main to see how that is used.
  */
  fn new() -> (Test3, JoinHandle<()>) {
    /* create mutex guarded i32 in heap memory */
    let scount = Arc::new(Mutex::new(0));
    /* create shared reference to that value */
    let share = Arc::clone(&scount);
    /* scount is moved into thread's closure */
    let handle = std::thread::spawn(move || {
      for i in 0..5 {
        let mut data = scount.lock().unwrap();
        *data += i;
        print!("\n  {:?}", data);
      }
    });
    /* share holds a valid ref to guarded i32 in heap */
    (
      Test3 { counter: share, },
      handle
    )
  }
  fn show_count(&self) {
    print!(
      "\n\n  t3 result = {:?}",
      self.counter.lock().unwrap()
    );
  }
}
              
            
            
              Using Code:
              
fn main() {
  print!("\n  -- demo Test3 --");
  let (t3, handle) = Test3::new();
  /* do some useful work here */
  let _ = handle.join();
  t3.show_count();
}
              
              Output:
              
-- demo Test3 --
0
1
3
6
10
t3 result = 10
              
            
          
        
        
          
          
          1.3 - User-defined Type with stop() method
        
        
          This example illustrates how to stop an asynchrounous method with a user command.
          The Test4 type provides an AtomicBool to record the user's intent to stop,
          and a method, stop(), to reset it.
        
        
          The method show_count() displays the number of thread iterations before being
          stopped by the user.
        
        
          
            
              Test4 - graceful thread stop
              
struct Test4 {
  counter: Arc<Mutex<i32>>,
  do_run : Arc<AtomicBool>,
}
impl Test4 {
  /*-----------------------------------------------------
    This example demonstrates graceful thread shutdown
    using an AtomicBool, set by the user and tested in
    the thread loop.
  */
  fn new() -> (Test4, JoinHandle<()>) {
    let scount = Arc::new(Mutex::new(0));
    let share = Arc::clone(&scount);
    let run = Arc::new(AtomicBool::new(true));
    let run_clone = Arc::clone(&run);
    let handle = std::thread::spawn(move || {
      for i in 0..5000 {
        if !run.load(Ordering::Relaxed) {
          break;
        }
        /* slow down loop for display */
        std::thread::sleep(
          Duration::from_micros(200)
        );
        let mut data = scount.lock().unwrap();
        *data = i;
        print!("\n  {:?}", data);
      }
    });
    (
      Test4 {
        counter: share, do_run: run_clone
      },
      handle
    )
  }
  fn stop(&self) {
    self.do_run.store(false, Ordering::Relaxed);
  }
  fn show_count(&self) {
    print!(
      "\n\n  t4 result = {:?}",
      self.counter.lock().unwrap()
    );
  }
}
              
            
            
              Using Code:
              
fn main() {
  print!(
    "\n  -- demo Test4 - graceful stop --"
  );
  let (t4, handle) = Test4::new();
  std::thread::sleep(
    Duration::from_millis(100)
  );
  t4.stop();
  let _ = handle.join();
  t4.show_count();
}
              
              Output:
              
-- demo Test4 - graceful stop --
0
1
2
3
4
5
6
7
t4 result = 7
              
            
          
        
        
        
        
        2.0 - Dates and Times
        
          This section presents date and time tools for testing and logging.
        
        
        
        2.1 - StopWatch
        
          StopWatch is a user-defined type that measures time that elapses
          between calls to StopWatch::start() and StopWatch::stop().
        
        
          I've used it to test the performance and throughtput of
          RustComm.
        
        
          
            
              StopWatch
              
extern crate chrono;
/*-----------------------------------------------
  StopWatch type measures elapsed times
*/
use std::time::*;
use std::{thread};
use std::thread::{JoinHandle};
use std::fmt::*;
#[derive(Debug, Clone, Copy)]
pub struct StopWatch {
  start: Instant,
  elapsed: Duration,
}
impl StopWatch {
  pub fn new() -> StopWatch {
    StopWatch {
      start: Instant::now(),
      elapsed: Duration::new(0,0),
    }
  }
  pub fn start(&mut self) {
    self.start = Instant::now();
  }
  pub fn stop(&mut self) -> Duration {
    self.elapsed = self.start.elapsed();
    self.elapsed
  }
  pub fn elapsed_micros(&self) -> u128  {
    self.elapsed.as_micros()
  }
  pub fn elapsed_millis(&self) -> u128 {
    self.elapsed.as_millis()
  }
  pub fn elapsed_secs(&self) -> u64 {
    self.elapsed.as_secs()
  }
}
fn sleep(millisec:u64) {
  let secs = Duration::from_millis(millisec);
  thread::sleep(secs);
}
              
            
            
              Using Code:
              
/*-----------------------------------------------
  stop_watch displays thread sleep time
  - expect small variations from run to run
    due to uncertainty in sleep interval
*/
fn stop_watch(millisec: u64) {
  let mut sw = StopWatch::new();
  sleep(millisec);
  let time = sw.stop().as_millis();
  print!("\n  elapsed time = {:?}",time);
}
fn main() {
  print!("\n  -- demo StopWatch --");
  stop_watch(25);
  println!();
}
              
              Output:
              
-- demo StopWatch --
elapsed time = 26
              
            
          
        
        
          
          
        
        2.2  Timer emits callback
        
          Timer start accepts a time in milliseconds and a closure to run at the end of that time.
        
        
          
            
              Timer
              
/*-----------------------------------------------
  Timer instance invokes callback after
  specified time.
  - callback runs on Timer thread
*/
#[derive(Debug, Clone, Copy)]
struct Timer {
  start: Instant,
  elapsed: Duration,
}
impl Timer {
  pub fn new(time: u64) -> Timer {
    Timer {
      start: Instant::now(),
      elapsed: Duration::new(time, 0),
    }
  }
  pub fn start<F>(&mut self, time: u64, callback:F)
  -> JoinHandle<()>
    where F:FnOnce() + Send + 'static {
    self.start = Instant::now();
    let handle = thread::spawn(move || {
      let ttw = Duration::from_millis(time);
      thread::sleep(ttw);
      callback();
    });
    handle
  }
}
/*-----------------------------------------------
  Demo Timer, using closure callback
*/
fn timer(millisec: u64) -> JoinHandle<()> {
  let mut tmr = Timer::new(millisec);
  let cl = move || {
    print!(
      "\n  time's up after {:?} milliseconds",
      millisec
    )
  };
  tmr.start(millisec, cl)
}
              
            
            
              Using Code:
              
fn main() {
  print!("\n  -- demo Timer --");
  print!("\n  starting timer(200)");
  let handle = timer(200);
  print!(
    "\n  do some work while waiting for timer"
  );
  let _ = handle.join();
  println!();
}
              
              Output:
              
-- demo Timer --
starting timer(200)
do some work while waiting for timer
time's up after 200 milliseconds
              
            
          
        
        
        
        2.3  Date-Time Stamps
        
          Date-time stamps are used for display, often while logging or recording
          test data.
        
        
          
            
              Time and Date
              
/*-----------------------------------------------
  Date-Time stamp
*/
#[allow(unused_imports)]
use chrono::{DateTime, Local, Datelike, Timelike};
pub fn date_time_stamp() -> String {
  let now: DateTime<Local> = Local::now();
  /*---------------------------------------------
    format DateTime string using chrono formatting
  */
  let mut now_str = format!("{}", now.to_rfc2822());
  /* remove trailing -0400 */
  now_str.truncate(now_str.len() - 6);
  now_str
}
fn convert_month(m:usize) -> &'static str {
  let dv = vec![
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  ];
  dv[m]
}
pub fn date_stamp() -> String {
  let now: DateTime<Local> = Local::now();
  /* format date string */
  let (_is_common_era, year) = now.year_ce();
  let idx:usize = (now.month() - 1) as usize;
  let mon = convert_month(idx);
  let now_str = format!(
    "{:0>2} {} {}",
    now.day(), mon, year
  );
  now_str
}
pub fn time_stamp() -> String {
  let now: DateTime<Local> = Local::now();
  /* format time string */
  let now_str = format!(
    "{:0>2}:{:0>2}:{:0>2}",
    now.hour(), now.minute(), now.second()
  );
  now_str
}
              
            
            
              Using Code:
              
fn main() {
  print!("\n  -- demo DateTimeStamp --");
  print!("\n  now is:  {:?}", date_time_stamp());
  print!("\n  date is: {:?}", date_stamp());
  print!("\n  time is: {:?}", time_stamp());
}
              
              Output:
              
-- demo DateTimeStamp --
now is:  "Sun, 26 Jul 2020 17:39:51"
date is: "26 Jul 2020"
time is: "17:39:51"
              
            
          
        
        
        
        3.0  Coming Soon
        
          
            - 
              string conversions
            
 
            - 
              cast only for primitives, else From and Into
            
 
            - 
              Traits - see donovan pain-points.html, section 10
            
 
            - 
              HashMap entry api (donovan again)
            
 
            - 
              Reading values of Move types from Arrays and Vectors
              
            
 
            - Living well with the ownership rules - pass-by-reference functions
 
            -  ...