about
RustStory Libraries
9/24/2022

Chapter 5. - Rust Libraries

std, collectons, fs, net, error, time, thread, process

5.0 Prologue

Rust has a very well-engineered collection of standard libraries. Each library presents trait, structure, function, and type definitions. The structure and function pages have examples for almost everything presented.
Source code is provided for all the std libraries. simply go here, browse around and click any of the [src] tabs.
Once you are passed the stage of constantly being surprised by ownership rules, you will find the libraries easy to use and rich in functionality.

5.1 std Libraries

Fig 1. Rust Std Libraries
Rust has a well endowed collection of more than sixty std libraries in the std namespace. You don't have to declare a use std::*, as the std::prelude::* is automatically applied. All of the libraries discussed below are part of std. There are many more - included here are libraries I expect you will use most often. The materials below summarize some of the Rust Library documentation, and give small examples of library use. Note that the Rust documentation has examples for almost everything. The examples here are often a little larger, and dig into things that interested me as I learned some of the library content.

5.2 std::convert - conversions between types

Traits for conversions are provided in std::convert. The traits provided in this module provide ways to convert from one type to another. Each trait serves a different purpose:
  • AsRef trait provides cheap (re-interprets) reference-to-reference conversions.
  • AsMut trait provides cheap (re-interprets) mutable-to-mutable conversions.
  • From trait provides consuming (move) value-to-value conversions.
  1. AsRef: pub trait AsRef<T> where T: ?Sized, { fn as_ref(&self) -> &T }
    Partial List of Implementors:
    • [T]::as_ref<[T]>() -> &[T];
    • Vec<T>::as_ref<[T]>() -> &[T];
    • Vec<T>::as_ref<Vec<T>>() -> &Vec<T>
    • Box<T>::as_ref<T>() -> &T;
      where T: ?Sized
    • Arc<T>::as_ref<T>() -> &T;
      where T: ?Sized
    • str::as_ref<[u8]>() -> &[u8];
    • str::as_ref<str>() -> &str;
    • str::as_ref<OsStr>() -> &OsStr;
    • String::as_ref<[u8]>() -> &[u8];
    • String::as_ref<str>() -> &str;
    • String::as_ref<OsStr>() -> &OsStr;
    • String::as_ref<Path>() -> &Path;
    • CStr::as_ref<CStr>() -> &CStr;
    • CString::as_ref<CStr>() -> &CStr;
    • OsStr::as_ref<OsStr>() -> &OsStr;
    • OsString::as_ref<OsStr>() -> &OsStr;
    • Path::as_ref<OsStr>() -> &OsStr;
    • PathBuf::as_ref<OsStr>() -> &OsStr;
    • PathBuf::as_ref<OsStr>() -> &Path;
  2. AsMut: pub trait AsMut<T> where T: ?Sized, { fn as_mut(&mut self) -> &mut T; }
    Partial List of Implementors
    • String::as_mut() -> &mut str
    • [T]::as_mut() -> &mut [T]
    • Vec<T>::as_mut() -> &mut [T]
    • Vec<T>::as_mut() -> &mut Vec<T>
    • Box<T: ?Sized>::as_mut() -> &mut T
  3. From: pub trait From<T> { fn from(T) -> Self }
    Partial List of Implementors
    • String::from(&str)
    • String::from(Box<str>)
    • char::from(u8)
    • PathBuf::from(String)
    • Pathbuf::from(OsString)
    • OsString::from(String)
    • OsString::From(PathBuf)
    • u32::from(char)
    • isize::from(i16)
    • f64::from(f32)
    • f64::from(i16)
    • AtomicI16::from(i16)
    • Vec<u8>::from(String)
    • Vec<T>::from(BinaryHeap<T>)
    • Vec<T>::from(VecDeque<T>)
    • VecDeque<T>::from(Vec<T>)
    • BinaryHeap<T>::from(Vec<T>)
    • Mutex<T>::from(T)
    • RWLock<T>::from(T)
    • Box<[u8]>::from(Box>str>)
    • Box<str>::from(&str)
    • Arc<OsStr>::from(OsString)
    • Arc<Path>::From(PathBuf)
    • Arc<String>::from(String)
    • Arc<T>::from(T)
    • Error::from(kind: ErrorKind)
    • Stdio::from(File)
    • Stdio::from(ChildStdin)
    • Stdio::from(ChildStdout)

5.3 std::collections - containers and iterators

The std namespace has a set of std::collections similar to other languages:
CollectionDescription
Vec<T> Sequence container:
A self expanding indexable array of items of type T, occupying a contiguous block of memory. Constant time access to indexed item, linear time insertion and removal.
VecDeque<T> Sequence container:
A self expanding indexable array of items of type T, with constant-time access, insertion, and removal on either end.
LinkedList<T> Sequence container:
A doubly linked list of items of type T, with linear-time traversal.
HashMap<K, V> Associative container:
A hashed container with almost constant time access. Elements are randomly ordered (if the hash function works well for T)
BTreeMap<K, V> Associative container:
A balanced binary tree container with log n time access. Elements are sorted.
HashSet<K> Set container:
A hashed container with almost constant time access. Elements are randomly ordered (if the hash function works well for T)
BTreeSet<K> Set container:
A balanced binary tree container with log n time access. Elements are sorted.
BinaryHeap<T> Priority queue:
Largest element is accessed in constant time.
Example:  std::collections::echo Collections_echo ///////////////////////////////////////////////////////////// // collections_echo - demonstrate collections library // // // // Jim Fawcett, https://JimFawcett.github.io, 14 May 2020 // ///////////////////////////////////////////////////////////// use std::collections::*; use std::fmt::*; /*-- displays type name and value --*/ fn show_type<T: Debug>(t:&T) { let name = std::any::type_name::<T>(); print!("\n type: {:?}", name); print!("\n {:?}", &t); } /*-- echo function shows the type and value of its arg --*/ /*--------------------------------------------------------- Illustrates how to accept and return iterable generic type */ fn echo<C: Debug + Clone + IntoIterator>(c:&C) -> &C where C::Item: Debug, { show_type(c); print!("\n items are: "); let iter = c.clone().into_iter(); // iter consumes for item in iter { print!("{:?} ", item); } c } /*-- demonstration --*/ fn main() { let putline = || { print!("\n"); }; print!("\n collections_echo"); print!("\n =================="); let v = vec![1,2,3]; let r = echo(&v); print!("\n\n echo fn returned:"); show_type(&r); putline(); let mut vd = VecDeque::<f64>::new(); vd.push_back(1.0); vd.push_front(2.5); vd.push_back(-1.5); let r = echo(&vd); print!("\n\n echo fn returned:"); show_type(&r); putline(); let mut hm = HashMap::<i32, &str>::new(); hm.insert(0,"zero"); hm.insert(1,"one"); hm.insert(2,"two"); let r = echo(&hm); print!("\n\n echo fn returned:"); show_type(&r); putline(); println!("\n That's all Folks!\n"); } Output: collections_echo ================== type: "alloc::vec::Vec" [1, 2, 3] items are: 1 2 3 echo fn returned: type: "&alloc::vec::Vec" [1, 2, 3] type: "alloc::collections::vec_deque::VecDeque" [2.5, 1.0, -1.5] items are: 2.5 1.0 -1.5 echo fn returned: type: "&alloc::collections::vec_deque::VecDeque" [2.5, 1.0, -1.5] type: "std::collections::hash::map::HashMap" {0: "zero", 1: "one", 2: "two"} items are: (0, "zero") (1, "one") (2, "two") echo fn returned: type: "&std::collections::hash::map::HashMap" {0: "zero", 1: "one", 2: "two"} That's all Folks!
Find code for this example here.

5.3.1 Iterators

Reference: std::iter Iterators support iterating through collections. They come in three flavors:
  • iter(), which iterates over &T
  • iter_mut(), which iterates over &mut T
  • into_iter(), which iterates over T
Note that into_iter is a consuming iterator, moving items from source to destination.
Iterators are lazy: they don't do anything until consumed, e.g.:
coll.iter().for_each(|x| { /* do something with x */ }); or for x in &coll { /* do something else with x */ };
Traits:
  1. trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item> fn count(self) -> usize fn last(self) -> Option<Self::Item> fn nth(&mut self, n: usize) -> Option<Self::Item> fn skip(self, n: usize) -> Skip<Self> fn step_by(self, step: usize) -> StepBy<Self> fn map<B, F>(self, f: F) -> Map<Self, F> where F: FnMut(Self::Item) -> B fn for_each<F>(self, f: F) where F: FnMut(Self::Item) fn filter<P>(self, predicate: P) -> Filter<Self, P> where P: FnMut(&Self::Item) -> bool ... }
  2. trait IntoIterator { type Item; type IntoIter: Iterator fn into_iter(self) -> Self::IntoIter }
Iterators provide a set of adapter methods for performing common operations on containers. Here's a longer partial list - see Trait std::iter::Iterator for more details:
Partial List of Iterator Adapters
  1. fn next(&mut self) -> Option<Self::Item>
    Advances and returns value.
  2. fn map<B, F>(self, f:F) -> Map<Self, F>
    where F: FnMut(Self::Item) -> B
    Takes a closure and creates an iterator that applies the closure to each item.
  3. fn filter<P>(self, predicate:P) -> Filter<Self, P>
    where P: FnMust(&Self::Item) -> bool
    Creates an iterator that determines if an item should be included.
  4. fn take(self, n:usize) -> Take<Self>
  5. fn take_while<P>(self, predicate: P) -> TakeWhile<Self, P>
    where P: FnMut(&Self::Item) -> bool
  6. fn fold<B, F>(self, init:B, f:F) -> B
    where FnMut(B, Self::Item) -> B
  7. fn skip(self, n:usize) -> Skip<Self>
  8. fn collect<B>(self) -> B
    where B: FromIterator<Self::Item>
  9. step_by(self, step:usize) -> StepBy<Self>
    Skips step items on each iteration
  10. nth(&mut self, n:usize) -> Option<Self::item>
    Returns nth item, counting from zero
  11. last(self) -> Option<Self::item>
    Consumes iterator, returning last item
  12. fn find<P>(&mut self, predicate: P) -> Option<Self::Item>
    where FnMut(&Self::Item) -> bool
  13. fn position<P>(&mut self, predicate: P) -> Option<usize>
    where P: FnMut(Self::Item) -> bool
  14. fn for_each<F>(self, f:F)
    where F: FnMut(Self::Item)
    Calls a closure on each element of an iterator.
  15. fn enumerate(self) -> Enumerate<Self>
    Creates an iterator returns a pair with current iteration count and next value.
  16. fn by_ref(&mut self) -> &mut Self
  17. fn all<F>(&mut self, f:F) -> bool
    where F: FnMut(Self::Item) -> bool
  18. fn any<F>(&mut self, f:F) -> bool
    where F: FnMut(Self::Item) -> bool
  19. fn count(self) -> usize
    Consumes iterator, counting number of iterations
  20. fn chain<U>(self, other: U) -> Chain<Self, <U as IntoIterator>::IntoIter>
    where U: IntoIterator<Self::Item>
  21. fn zip<U>(self, other: U) -> Zip<Self, <U as IntoIterator>::IntoIter>
    where U: IntoIterator
  22. fn sum<S>(self) -> S
    where S: Sum<Self::Item>
  23. fn product<P>(self) -> P
    where P: Product<Self::Item>
Example:  iteration_echo iteration_echo ///////////////////////////////////////////////////////////// // iter_echo::main.rs - demonstrating iteration // // // // Jim Fawcett, https://JimFawcett.github.io, 14 May 2020 // ///////////////////////////////////////////////////////////// use std::collections::*; fn main() { let putline = || print!("\n"); let mut hm: HashMap<i32, &str> = HashMap::new(); hm.insert(0, "zero"); hm.insert(1, "one"); hm.insert(2, "two"); hm.insert(3, "three"); hm.insert(4, "four"); hm.insert(5, "five"); hm.insert(6, "six"); let iter = hm.iter(); print!("\n iterating over hashmap:\n "); for item in iter { print!("{:?}", item); } putline(); /*-- previous iter consumed so make new one --*/ let iter = hm.iter(); print!("\n iterating and selecting odd entries:\n "); for item in iter.step_by(2) { print!("{:?}", item); } putline(); /*-- previous iter consumed so make new one --*/ let iter = hm.iter(); print!("\n iterating and selecting even entries:\n "); for item in iter.skip(1).step_by(2) { print!("{:?}", item); } putline(); println!("\n That's all Folks!\n"); } Output: iterating over hashmap: (4, "four")(6, "six")(2, "two")(1, "one") (0, "zero")(3, "three")(5, "five") iterating and selecting odd entries: (4, "four")(2, "two")(0, "zero")(5, "five") iterating and selecting even entries: (6, "six")(1, "one")(3, "three") That's all Folks!
Find code for this example here. Similar to the C++ std::algorithms, iterators provide a readable declarative interface for code doing compound operations on collection items.

5.4 std::io - Base I/O, console operations

Reference: std::io Traits: Traits are contracts with function signatures that implementors are required to provide. For example, every implementor of the Read trait is obligated to provide read and all the other methods from that trait. The BufReader and Stdin structs both implement Read. In the dropdown, below, you will find the most important methods for the Read, BufRead, Seek, and Write traits.
Partial List of Traits
  1. pub trait Read { fn read(&mut self, buf: &mut [u8]) -> Result<usize> fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> fn read_to_string(&mut self, buf: &mut String) -> Result<usize> fn by_ref(&mut self) -> &mut Self, where Self: Sized, fn bytes(self) -> Bytes<Self>, where Self: Sized, ... }
  2. pub trait BufRead { fn fill_buf(&mut self) -> Result<&[u8]> fn consume(&mut self, amt: usize) fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize> fn read_line(&mut self, buf: &mut String) -> Result<usize> fn lines(self) -> Lines<Self> // an iterator ... }
  3. pub trait Write { fn write_all(&mut self, buf: &[u8]) -> Result<()> fn write(&mut self, buf: &[u8]) -> Result<usize> fn write_fmt(&mut self, fmt: Arguments) -> Result<()> fn by_ref(&mut self) -> &mut Self, where Self: Sized, fn flush(&mut self) -> Result<()>, ... }
  4. pub trait Seek { fn seek(&mut self, pos: SeekFrom) -> Result<u64> fn stream_len(&mut self) -> Result<u64> ... }
Structs: Structs are used to build library and user-defined types. Here, you will find definitions for BufReader, BufWriter, Error, LineWriter, Stdin, and Stdout types. Two other structs, Bytes and Lines, are iterator types that provide access to the io state. You will find definitions for all of these in the dropdown, below.
Partial List of Structs
  1. BufReader Implements Traits BufRead, Debug, Read, Seek, ... pub fn new(inner: R) -> BufReader<R> where R: Read pub fn get_ref(&self) -> &R pub fn get_mut(&self) -> &mut R pub fn buffer(&self) -> &[u8] pub fn into_inner(self) -> R
  2. BufWriter Implements Traits Debug, Drop, Seek, Write ... pub fn new(inner: W) -> BufWriter<W> where W: Write pub fn get_ref(&self) -> &W pub fn get_mut(&self) -> &mut W pub fn buffer(&self) -> &[u8] pub fn into_inner(self) -> Result<W, IntoInnerError<BufWriter<W>>>
  3. Bytes Implements Traits Debug, Iterator ...
  4. Error Implements Traits Debug, Display, Error, From<ErrorKind>, ... pub fn new<E>(kind: ErrorKind, error: E) -> Error pub fn get_ref(&self) -> Option<&(dyn Error + Send + Sync + 'static')> pub fn get_mut(&mut self) -> Option<&mut (dyn Error + Send + Sync + 'static')> pub fn into_inner(self) -> Option<Box<dyn Error + Send + Sync>> pub fn kind(&self) -> ErrorKind
  5. LineWriter Implements Traits Debug, Write ... pub fn new(inner: W) -> LineWriter<W> where W: Write pub fn get_ref(&self) -> &W pub fn get_mut(&self) -> &mut W pub fn into_inner(self) -> Result<W, IntoInnerError<LineWriter<W>>>
  6. Lines Implements Traits Debug, Iterator ...
  7. Stdin Implements Traits Debug, Read ... pub fn lock(&self) -> StdinLock pub fn read_line(&self, buf: &mut String) -> Result<usize>
  8. Stdout Implements Traits Debug, Write ... pub fn lock(&self) -> StdoutLock
Functions: Here you will find a short list of functions that are occasionally useful for programs dealing with io.
Partial List of Functions
  1. pub fn copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> Result<u64>
  2. pub fn stderr() -> Stderr
  3. pub fn stdin() -> Stdin
  4. pub fn stdout() -> Stdout
Types:
type Result<T> = Result<T, Error>
You will find a relatively simple std::io echo program in the dropdown, below.
Example:  std::io::echo io_echo ///////////////////////////////////////////////////////////// // io_echo::main.rs - std::io library demonstration // // // // Jim Fawcett, https://JimFawcett.github.io, 14 May 2020 // ///////////////////////////////////////////////////////////// use std::io::prelude::*; fn main() -> std::io::Result<()> { print!("\n Echo std::io"); print!("\n =============="); const QUIT:char = 'q'; print!("\n Enter single \'q\' character to terminate program"); loop { print!("\n enter some text: "); let _ = std::io::stdout().flush(); let mut input_str = String::new(); let size = std::io::stdin().read_line(&mut input_str)?; ///////////////////////////////////////////////////////////// // line below needs ctrl z to terminate read // let size = std::io::stdin().read_to_string(&mut input_str)? print!("\n read {} bytes", size); let rslt = input_str.chars().nth(0); if rslt == Some(QUIT) && size == 3 { break; } let out_str = format!("\n {:?}", input_str); std::io::stdout().write_all(out_str.as_ref())?; // convert to &[u8] std::io::stdout().flush()?; } println!("\n That's all Folks!"); Ok(()) } Output Echo std::io ============== Enter single 'q' character to terminate program enter some text: this is a test read 16 bytes "this is a test\r\n" enter some text: another line read 14 bytes "another line\r\n" enter some text: q read 3 bytes That's all Folks!
You will find the example code here.

5.5 std::fs - File system operations

Working with files and directories is well supported in Rust with the std::fs library. Structs: The most important std::fs types are listed here - DirBuilder, DirEntry, File, FileType, Metadata, OpenOptions, Permissions, and ReadDir:
List of Structs
  1. DirBuilder Implements Traits Debug, ... pub fn new() -> DirBuilder pub fn create<P: AsRef<Path>>(&mut self, path: P) -> Result<()> pub fn recursive(&mut self, recursive: bool) -> &mut Self
  2. DirEntry Implements Traits Debug, ... pub fn path(&self) -> PathBuf pub fn metadata(&mut self) -> Result<Metadata> pub fn metadata(&mut self) -> Result<Metadata> pub fn metadata(&mut self) -> Result<Metadata> pub fn file_name(&mut self) -> OsString
  3. File Implements Traits Debug, Read, Seek, Write, ... pub fn open<P: AsRef<Path>>(path: P) -> Result<File> pub fn create<P: AsRef<Path>>(path: P) -> Result<File> pub fn set_len(&mut self, size: u64) -> Result<()> pub fn metadata(&mut self) -> Result<Metadata> pub fn set_permissions(&mut self, perm: Permissions) -> Result<()>
  4. FileType Implements Traits Clone, Copy, Debug, ... pub fn is_dir(&mut self) -> bool pub fn is_file(&mut self) -> bool
  5. Metadata Implements Traits Clone, Debug, ... pub fn file_type(&mut self) -> FileType pub fn is_dir(&mut self) -> bool pub fn is_file(&mut self) -> bool pub fn len(&mut self) -> u64 pub fn permissions(&mut self) -> Permissions pub fn modified(&mut self) -> Result<SystemTime> pub fn accessed(&mut self) -> Result<SystemTime> pub fn created(&mut self) -> Result<SystemTime>
  6. OpenOptions Implements Traits Clone, Debug, ... pub fn new() -> OpenOptions pub fn read(&mut self, read: bool) -> &mut OpenOptions pub fn write(&mut self, write: bool) -> &mut OpenOptions pub fn append(&mut self, append: bool) -> &mut OpenOptions pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions pub fn create(&mut self, create: bool) -> &mut OpenOptions pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<File>
  7. Permissions Implements Traits Clone, Debug, Eq, PartialEq, ... pub fn readonly(&self) -> bool pub fn set_readonly(&mut self, readonly: bool)
  8. ReadDir Implements Traits Debug, Iterator, ...
    Iterator over entries in a directory, returned from the read_dir(p: Path) function. Yields instances of io::Result<DirEntry>
Functions: Quite often, you may only need some of the functions, listed here, for a program's file and directory management needs.
Partial List of IO Functions
  1. fn canonicalize<P: AsRef<Path>>(path: P) -> Result<PathBuf>
  2. fn create_dir<P: AsRef<Path>>(path: P) -> Result<()>
  3. fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir>
  4. fn remove_dir<P: AsRef<Path>>(path: P) -> Result<()>
  5. fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata>
  6. fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>>
  7. fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir>
  8. fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String>
  9. fn write<P: AsRef<Path>>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
  10. fn remove_file<P: AsRef<Path>>(path: P) -> Result<()>
  11. fn rename<P: AsRef<Path>>(path: P) -> Result<()>
  12. fn set_permissions<P: AsRef<Path>>(path: P, perm: Permissions) -> Result<()>
Example:  std::fs::echo fs_echo::main.rs ///////////////////////////////////////////////////////////// // fs_echo::main.rs - demonstrate file operations // // // // Jim Fawcett, https://JimFawcett.github.io, 15 May 2020 // ///////////////////////////////////////////////////////////// use std::fs::*; use std::io::*; /*----------------------------------------------------------- Note: The try operator ? has been used in several places in both using_File and using_OpenOptions to avoid a lot of explicit error handling. The try operator bubbles errors up to the caller, so if there are several places where errors may occur, they can all be handled in one place by the caller. What is surprising, but useful, is that the Result<T,E> types are not all the same. In some results the type T is File, or (), or usize. However, the error types are all io::Error, and that is what ? returns to the caller, so it just works! If there are no errors, ? simply unwraps the result to be used, and everything is significantly simpler. */ #[allow(non_snake_case)] fn using_File(file_name:&str, msg:&str) -> std::io::Result<()> { let mut wfile = File::create(file_name)?; print!( "\n writing message {:?} to file {:?}", msg, file_name ); wfile.write_all(msg.as_bytes())?; let mut rfile = File::open(file_name)?; let mut rcv_msg = String::new(); let _ = rfile.read_to_string(&mut rcv_msg); print!( "\n message {:?} read from {:?}", rcv_msg, file_name ); Ok(()) } fn open_file_for_write(path:&str) -> std::io::Result<File> { let wfile = OpenOptions::new() .create(true) .write(true) .truncate(true) .open(path)?; Ok(wfile) } fn open_file_for_read(path:&str) -> std::io::Result<File> { let rfile = OpenOptions::new() .read(true) .open(path)?; Ok(rfile) } #[allow(non_snake_case)] fn using_OpenOptions(file_name:&str, msg:&str) -> std::io::Result<()> { let mut wfile = open_file_for_write(file_name)?; print!( "\n writing message {:?} to file {:?}", msg, file_name ); wfile.write_all(msg.as_bytes())?; let mut rfile = open_file_for_read(file_name)?; let mut rcv_msg = String::new(); let _ = rfile.read_to_string(&mut rcv_msg); print!( "\n message {:?} read from {:?}", rcv_msg, file_name ); Ok(()) } fn main() -> std::io::Result<()> { let putline = || print!("\n"); let file_name = "test.txt"; let msg = "msg from writer"; print!("\n std::fs echo demonstration"); print!("\n ============================"); putline(); print!("\n demo using File structure"); print!("\n ---------------------------"); using_File(file_name, msg)?; putline(); print!("\n demo using OpenOptions structure"); print!("\n ----------------------------------"); using_OpenOptions(file_name, msg)?; println!("\n\n That's all Folks!\n"); Ok(()) } Output: std::fs echo demonstration ============================ demo using File structure --------------------------- writing message "msg from writer" to file "test.txt" message "msg from writer" read from "test.txt" demo using OpenOptions structure ---------------------------------- writing message "msg from writer" to file "test.txt" message "msg from writer" read from "test.txt" That's all Folks!
The code for this example can be bound here.

5.6 net: - Socket operations

Reference: std::net library. Structs: The std::net types are: TcpListener, Incoming, TcpStream, UdpSocket, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, AddrParseError, ...
List of Structs
  1. TcpListener Traits: Debug, ... pub fn bind<A: ToSocketAddrs>(addr: A) -> Result<TcpListener> pub fn accept(&self) -> Result<(TcpStrea,. SpcletAddr)> pub fn inoming(&self) -> Incoming pub fn take_error(&self) -> Result<Option<Error>> pub fn set_nonblocking(&self, nonblocking: bool) -> Result<()> ...
  2. TcpStream Traits: Read, Write, Debug, ... pub fn connect<A: ToSoketAddrs>(addr: A) -> Result<TcpStream> pub fn shutdown(&self, how: Shutdown) -> Result<()> pub fn peek(&mut self, buf: &mut [u8]) -> Result<usize> pub fn take_error(&self) -> Result<Option<Error>> pub fn set_nonblocking(&self, nonblocking: bool) -> Result<()> ...
  3. UdpSocket Traits: Debug, ... pub fn bind<A: ToSocketAddrs>(addr: A) -> Result<TcpListener> pub fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr)> pub fn peek_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr)> pub fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> Result<usize> pub fn set_broadcast(&self, broadcast: bool) -> Result<()> pub fn broadcast(&mut self) -> Result<bool> pub fn take_error(&self) -> Result<Option<Error>> pub fn connect<A: ToSocketAddrs>(&self, addr: A) -> Result<()> pub fn send(&self, buf: &mut [u8]) -> Result<usize> pub fn recv(&self, buf: &mut [u8]) -> Result<usize> pub fn peek(&self, buf: &mut [u8]) -> Result<usize> pub fn set_nonblocking(&self, nonblocking: bool) -> Result<()> ...
  4. Ipv4Addr Implements Traits Debug, Display, copy, clone, Eq, PartialEq, From ... pub const fn new(a: u8, b: u8, c: u8, d: u8) -> IpvrAddr pub const LOCALHOST: Self pub const UNSPECIFIED: Self pub const BROADCAST: Self pub fn octets(&self) -> [u8; 4] pub fn is_unspecified(&self) -> bool pub fn is_loopback(&self) -> bool pub fn is_private(&self) -> bool pub fn is_multicast(&self) -> bool pub fn is_broadcast(&self) -> bool pub fn to_ipv6_compatible(&self) -> Ipv6Addr pub fn to_ipv6_mapped(&self) -> Ipv6Addr
  5. Ipv6Addr Implements Traits Debug, Display, copy, clone, Eq, PartialEq, From ... pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> IpvrAddr pub const LOCALHOST: Self pub const UNSPECIFIED: Self pub fn segments(&self) -> [u16; 8] pub fn octets(&self) -> [u8; 16] pub fn is_unspecified(&self) -> bool pub fn is_loopback(&self) -> bool pub fn is_multicast(&self) -> bool pub fn to_ipv4(&self) -> Ipv4Addr
  6. SocketAddrV4 Traits: Debug, Display, Copy, Clone, Eq, PartialEq, From, FromStr, ToSocketAddrs, ... pub fn new(ip: Ipv4Addr, port: u16) -> SocketAddrV4 pub fn ip(&self) -> &Ipv4Addr pub fn set_ip(&mut self, new_ip: Ipv4Addr) pub fn port(&self) -> u16 pub fn set_port(&mut self, new_port: u16)
  7. SocketAddrV6 Traits: Debug, Display, Copy, Clone, Eq, PartialEq, From, FromStr, ToSocketAddrs, ... pub fn new(ip: Ipv6Addr, port: u16, flowinfo: u32, scope_id: u32) -> SocketAddrV6 pub fn ip(&self) -> &Ipv6Addr pub fn set_ip(&mut self, new_ip: Ipv6Addr) pub fn port(&self) -> u16 pub fn set_port(&mut self, new_port: u16) pub fn flowinfo(&self) -> u32 pub fn set_flowinfo(&mut self, new_flowinfo: u32) pub fn scope_id(&self) -> u32 pub fn set_scope_id(&mut self, new_scope_id: u32)
  8. AddrParseError Traits: Error, Debug, Display, Clone, Eq, PartialEq, ...
Enums: The net library has enums: IpAddr, SocketAddr, Shutdown, and Ipv6MulitcastScope.
List of net Enums
  1. pub enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr), }
  2. pub enum SocketAddr { V4(SocketAddrV4), V6(SocketAddrV6), }
  3. pub enum Shutdown { Read, Write, Both, }
  4. pub enum Ipv6MulticastScope {
        InterfaceLocal, LinkLocal, RealmLocal, AdminLocal, SiteLocal, OrganizationLocal, Global,
    }
Example:  std::net::echo net_echo::main.rs ///////////////////////////////////////////////////////////// // net_echo::main.rs - demonstrate Tcp communication // // // // Jim Fawcett, https://JimFawcett.github.io, 16 May 2020 // ///////////////////////////////////////////////////////////// /* This demo uses TcpListener and TcpStream which use sockets internally. So, like sockets, they are byte oriented. In order to send messages, the application needs a protocol for defining when to stop reading bytes. For this, newlines are used as message terminators, which works well for this demonstration. An application will probably need something more flexible like HTTP style messages. */ use std::io::prelude::*; use std::net::{TcpStream, TcpListener, Shutdown::Both}; use std::thread; use std::str; use std::io::{BufReader}; /*-- demo handler receives one msg and replys --*/ fn handle_client(mut stream: &TcpStream) -> std::io::Result<()> { print!("\n entered client handler"); let mut reader = BufReader::new(stream.try_clone()?); let mut rcv_msg = String::new(); reader.read_line(&mut rcv_msg)?; rcv_msg.pop(); // remove '\n' sentinal print!("\n client handler received: "); print!("{:?}",rcv_msg); let _ = std::io::stdout().flush(); rcv_msg.push_str(" recieved!\n"); let _ = std::io::stdout().flush(); stream.write_all(&rcv_msg.as_bytes())?; stream.shutdown(Both)?; Ok(()) } /*-- demo listener accepts only one connection --*/ fn start_listener(end_point :&str) -> std::io::Result<()> { print!( "\n starting listener on {:?}", end_point ); let _ = std::io::stdout().flush(); let tcpl = TcpListener::bind(end_point)?; for stream in tcpl.incoming() { print!("\n listener accepted connection"); let _ = handle_client(&stream?)?; /*-- Here, just accept one connection --*/ break; } Ok(()) } /*-- demonstration --*/ fn main() -> std::io::Result<()> { print!("\n net_echo demonstration"); print!("\n ========================"); let rcvr_endpoint = "127.0.0.1:8080"; let mut msg = "msg from connector".to_string(); /*-- run listener on child thread --*/ let handle = thread::spawn( move || { let _ = start_listener(rcvr_endpoint); } ); /*-- send message --*/ print!("\n connecting to {:?}", rcvr_endpoint); let mut stream = TcpStream::connect(rcvr_endpoint)?; print!("\n sending message {:?}", msg); msg.push('\n'); // message end sentinal stream.write_all(&msg.as_bytes())?; /*-- read reply message --*/ let mut reader = BufReader::new(stream.try_clone()?); let mut rcv_msg = String::new(); reader.read_line(&mut rcv_msg)?; rcv_msg.pop(); // remove '\n' sentinal print!( "\n connector received reply {:?}", rcv_msg ); let _ = std::io::stdout().flush(); let _ = handle.join(); // wait for shutdown println!("\n\n That's all Folks!\n\n"); Ok(()) } Output: net_echo demonstration ======================== connecting to "127.0.0.1:8080" starting listener on "127.0.0.1:8080" sending message "msg from connector" listener accepted connection entered client handler client handler received: "msg from connector" connector received reply "msg from connector recieved!" That's all Folks!
The code for this example can be found here.

5.7 Thread - basic threads

Reference: std::thread library. Thread related Traits:
  1. trait Send { } // marker trait // - types that can be transferred across thread boundaries
  2. trait Sync { } // marker trait // - types for which it is safe to share references between threads
Structs: The std::thread types are: Thread, Builder, ThreadId, JoinHandle, LocalKey, AcessError
List of Structs
  1. Thread Traits: Debug, Clone, Send, Sync, ... pub fn id(&self) -> ThreadId pub fn name(&self) -> Option<&str>
  2. ThreadId Traits: Debug, Copy, Clone, Eq, PartialEq, Send, Sync, ...
  3. Builder Traits: Debug, Send, Sync, ... pub fn new() -> Builder pub fn name(self, name: String) -> Builder pub fn stack_size(self, size: usize) -> Builder pub fn spawn<F, T>(self, f: F) -> Result<JoinHandle<T>> where F: FnOnce() -> T, F: Send + 'static', T: Send + 'static'
  4. JoinHandle Traits: Debug, Send, Sync, ... pub fn thread(&self) -> &Thread pub fn join(self) -> Result<T>
  5. LocalKey Traits: Debug, Send, Sync, ... pub fn with<F, R>(&'static self, f: F) -> R where F: FnOnce(&T) -> R ...
  6. AccessError Traits: Debug, Display, Clone, Copy, Eq, PartialEq, Error, Send, Sync, ...
Thread functions are: spawn, current, sleep, yield_now, ...
List of Functions
  1. pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static
  2. pub fn current() -> Thread
  3. pub fn sleep(dur: std::time::Duration)
  4. pub fn yield_now()
  5. ...
Types:
type Result<T> = Result<T, Box<dyn Any + Send + 'static>>
Example:  std::thread::Shared std::thread::shared ///////////////////////////////////////////////////////////// // thread_shared::main.rs - shared string demo // // // // Jim Fawcett, https://JimFawcett.github.io, 10 May 2020 // ///////////////////////////////////////////////////////////// #![allow(dead_code)] use std::thread; use std::sync::{Arc, Mutex}; use std::time::Duration; fn db_show<T:std::fmt::Debug>(t:T, msg:&str, p:bool) { print!("\n --{}", msg); if p { let name = std::any::type_name::<T>(); print!( "\n --TypeId: {}, \n --size: {}", name, std::mem::size_of::<T>() ); } print!("\n --{:?}", t); } fn thread_id() -> thread::ThreadId { thread::current().id() } fn thread_id_value(id:thread::ThreadId) -> char { let sid = format!("{:?}", id); sid.chars().nth(9).unwrap() } fn test2() -> std::io::Result<()> { print!("\n Two child threads sharing string"); print!("\n =================================="); let thrd1_id = thread_id_value(thread::current().id()); print!("\n main thread id = {:?}", thrd1_id); let dur = Duration::from_millis(2); // sleep time let mut s = String::new(); // shared string s.push(thrd1_id); // main thread gets first edit let data = Arc::new(Mutex::new(s)); // thread safe shared wrapper /*------------------------------------------------------- Modify String s on child thread #1 */ let shared1 = Arc::clone(&data); // clones pointer to data ref let _handle1 = thread::spawn(move || { // shared1 clone moved without moving data let sid:String = format!("{:?}",thread::current().id()); // sid = "ThreadId(2)" if let Some(sid) = sid.chars().nth(9) { // ThreadId(2) => 2 for _i in 0..15 { { if let Ok(mut temp) = shared1.lock() { temp.push(sid); // append thread id } } // unlocked here thread::sleep(dur); } } }); /*------------------------------------------------------- Modify String s on child thread #2 */ let shared2 = Arc::clone(&data); // db_show(&shared2, "shared2", false); let _handle2 = thread::spawn(move || { let sid:String = format!("{:?}",thread::current().id()); if let Some(sid) = sid.chars().nth(9) { for _i in 0..15 { { if let Ok(mut temp) = shared2.lock() { temp.push(sid); } } thread::sleep(dur); } } }); /*------------------------------------------------------- main thread displaying child threads ids and then joining them, e.g., block until threads complete */ let thrd2_id = thread_id_value(_handle1.thread().id()); print!("\n 1st child thread id = {:?}", thrd2_id); let _ = _handle1.join(); let thrd3_id = thread_id_value(_handle2.thread().id()); print!("\n 2nd child thread id = {:?}", thrd3_id); let _ = _handle2.join(); /*------------------------------------------------------- extract mutex from arc - commented line is simple but may panic - code used here manages error without panic */ // let out = Arc::try_unwrap(data).expect("lock still owned"); let rslt = Arc::try_unwrap(data); let out :Mutex<String>; match rslt { Ok(mtx) => out = mtx, Err(_) => { let error = std::io::Error::new( std::io::ErrorKind::Other, "Arc access error" ); return Err(error); }, } /*------------------------------------------------------- extract string from mutex - commented line is simple but may panic - code used here manages error without panic */ //let mut shared = out.into_inner().expect("can't lock mutex"); let mut shared: String; let rslt = out.into_inner(); match rslt { Ok(s) => shared = s, Err(_) => { let error = std::io::Error::new( std::io::ErrorKind::Other, "Mutex access error" ); return Err(error); } } shared.push(thrd1_id); // main thread has last edit /*-- display result of string modifications --*/ print!("\n final shared string value = {:?}",shared); Ok(()) } fn main() -> std::io::Result<()> { test2()?; println!("\n\n That's all Folks!\n\n"); Ok(()) } Output: Two child threads sharing string ================================== main thread id = '1' 1st child thread id = '2' 2nd child thread id = '3' final shared string value = "12323322332233223323232323223231" That's all Folks!
The code for this example can be found in RustLibraryDemos.

5.8 sync - Serialization with Mutexes, Condvars

Reference: std::sync library. Structs: The std::sync types are: Arc, Barrier, BarrierWaitResult, Condvar, Mutex, MutexGuard, Once, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, WaitTimeoutResult, Weak, OnceState
List of Structs
  1. Arc Traits: Debug, Display, Default, AsRef<T>, Deref, From, FromIterator, Eq, PartialEq, Pointer, Send, Sync, ... pub fn new(data: T) -> Arc<T> pub fn try_unwrap(this: Arc<T>) -> Result<T, Arc<T>> pub fn downgrade(this: &Arc<T>) -> Weak<T> pub fn weak_count(this: &Arc<T>) -> usize pub fn strong_count(this: &Arc<T>) -> usize pub fn ptr_eq(this: &Arc<T>, other: &Arc<T>) -> bool pub fn make_mut(this: &Arc<) -> &mut T where T: Clone pub fn get_mut(this: &Arc<) -> Option<&mut T> where T: ?Sized
  2. Mutex Traits: Debug, Default, for, Send, Sync... pub fn new(t: T) -> Mutex<T> pub fn lock(&self) -> LockResult<MutexGuard<T>> where T: ?Sized pub fn try_lock(&self) -> TryLockResult<MutexGuard<T>> where T: ?Sized pub fn is_poisoned(&self) -> bool pub fn into_inner(self) -> LockResult<T> where T: Sized pub fn get_mut(&mut self) -> LockResult<&mut T> ...
  3. MutexGuard Traits: Debug, Default, Send, Sync, ...
  4. Condvar Traits: Debug, Default, Send, Sync, ... pub fn new() -> ConVar pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> LockResult<MutexGuard<'a, T>> pub fn wait_while<'a, T, F>(&self, guard: MutexGuard<'a, T>, condition: F) -> LockResult<MutexGuard<'a, T>> where F: FnMut(&mut T) -> bool pub fn wait_timeout<'a, T>(&self, guard: MutexGuard<'a, T>, dur: Duration) -> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)> pub fn notify_one(&self) pub fn notify_all(&self)
  5. RwLock Traits: rDebug, Default, From, Send, Sync, ... pub fn new(t: T) -> RwLock<T> pub fn read(&self) -> LockResult<RwLockReadGuard<T>>
  6. RwLockReadGuard Traits: Debug, Display, Deref, !Send, Sync, ...
  7. RwLockWriteGuard Traits: Debug, Display, Deref, DerefMut, !Send, Sync, ...
  8. Weak Traits: Debug, Default, Clone, Send, Sync, ... pub fn new() -> Weak<T> pub fn upgrade(&self) -> Option<Arc<T>> pub fn strong_count(&self) -> usize pub fn ptr_eq(&self, other: &Weak<T>) -> bool
  9. WaitTimeoutResult Traits: Debug, Copy, Clone, Eq, PartialEq, Send, Sync, ... pub fn timed_out(&self) -> bool
  10. Once Traits: Debug, Send, Sync, ... pub const fn new(&self) -> Once pub fn call_once<F>(&self, f: F) where F: FnOnce() pub fn is_completed<F>(&self) -> bool
  11. Barrier Traits: Debug, Send, Sync, ... pub fn new(n: usize) -> Barrier pub fn wait(&self) -> BarrierWaitResult
  12. BarrierWaitResult Traits: Debug, Send, Sync, ...
  13. ...
Sync enum:
pub enum TryLockError<T> { Poisoned(PoisonError<T>), WouldBlock, }
Sync type definitions:
type LockResult<Guard> = Result<Guard, PoisonError<Guard>>;
type TryLockResult<Guard> = Result<Guard, TryLockError<Guard>>;
The example below builds a loosely coupled set of parts, using Arc, Mutex, and Condvar, that act like a blocking queue and demonstrate sending messages. Focus here on the use of synchronization constructs to avoid data races (the code won't compile if you don't use them).
Example:  sync_demo Example:  std::sync - Blocking Queue Parts ///////////////////////////////////////////////////////////// // sync_demo::main.rs - BlockingQueue Prototype // // // // Jim Fawcett, https://JimFawcett.github.io, 19 May 2020 // ///////////////////////////////////////////////////////////// /* This is a prototype for a blocking queue, essentially some pieces that act like a blocking queue, but not yet put into an abstraction for that. In the next demo we build a nice abstraction for this behavior. */ #![allow(clippy::mutex_atomic)] use std::io::*; use std::{time}; use std::sync::atomic::*; fn demo() { use std::sync::{Arc, Mutex, Condvar}; use std::thread; use std::collections::*; /*--------------------------------------------- shared queue and condition variable - condition variable makes thread wait for queue to have at least one entry - It is the reason queue blocks dequeuer when empty. - Arcs are thread-safe pointers, so both shared1 and its clone, shared2, refer to the same locks and queue. */ let shared1 = Arc::new(( Mutex::new(VecDeque::<String>::new()), Condvar::new(), )); let shared2 = shared1.clone(); /*--------------------------------------------- Atomic used to poll for active thread */ let thread_started = Arc::new(AtomicBool::new(false)); let checker = thread_started.clone(); /*--------------------------------------------- Start child thread - Dequeues messages sent by main thread. */ let handle = thread::spawn(move|| { print!("\n thread started"); thread_started.store(true, Ordering::SeqCst); let _time_delay = time::Duration::from_millis(55); let (lq, cvar) = &*shared2; loop { //thread::sleep(_time_delay); let item: String; { /*-- acquire lock --*/ let mut q = lq.lock().unwrap(); /*-- block on empty queue --*/ while q.len() == 0 { // may get spurious returns q = cvar.wait(q).unwrap(); } /*-- dequeue and display message --*/ item = q.pop_front().unwrap(); } // lock released print!("\n dequeued {:?} on child thread", item); let _ = std::io::stdout().flush(); /*-- client shuts down dequeuer with quit msg --*/ if item == "quit" { break; } } print!("\n thread finishing"); }); /*--------------------------------------------- Main thread thread enqueues messages for child thread. */ /*-- wait for child thread to start ---------*/ let _time_delay = time::Duration::from_micros(10); while !checker.load(Ordering::SeqCst) { thread::sleep(_time_delay); } /*-- start sending messages --*/ let (lq, cvar) = &*shared1; let mut not_processed = 0; let max = 5; for i in 0..max { let mut value:String; if i < max-1 { value = String::from("msg #"); value.push_str(&i.to_string()); } else { value = "quit".to_string(); } print!("\n enqueue {:?} on main thread", &value); { let mut q = lq.lock().unwrap(); q.push_back(value); not_processed = q.len(); } cvar.notify_one(); } /*--------------------------------------------- Make sure all queued items are processed: - Needed because notifies that are issued before thread starts are dropped. */ for _i in 0..not_processed { cvar.notify_one(); } print!("\n waiting for child thread to finish"); let _ = handle.join(); } fn main() { print!("\n Blocking queue shared between threads"); print!("\n ======================================="); demo(); print!("\n\n That's all Folks!\n"); } Output: Blocking queue shared between threads ======================================= thread started enqueue "msg #0" on main thread dequeued "msg #0" on child thread enqueue "msg #1" on main thread enqueue "msg #2" on main thread enqueue "msg #3" on main thread enqueue "quit" on main thread waiting for child thread to finish dequeued "msg #1" on child thread dequeued "msg #2" on child thread dequeued "msg #3" on child thread dequeued "quit" on child thread thread finishing That's all Folks!
Next, we use these ideas to provide a good BlockingQueue abstraction, without using unsafe code, that has essentially the same behavior.
Example:  Blocking Queue blocking_queue::main.rs ///////////////////////////////////////////////////////////// // sync_demo::main.rs - BlockingQueue // // // // Jim Fawcett, https://JimFawcett.github.io, 19 May 2020 // ///////////////////////////////////////////////////////////// /* This is a BlockingQueue abstraction. To be shared between threads without using unsafe code, any abstraction must be composed only of Mutexes and Condvars or a struct or tuple with only those members. So, the blocking queue must hold its native queue in a Mutex, as shown below. There is another alternative, based on Rust channels, which are essentially blocking queues. In another demo I will build a prototype from channels, and then discuss the advantages and disadvantages of each. */ use std::io::*; use std::sync::*; use std::collections::*; use std::thread; #[derive(Debug)] struct BlockingQueue<T> { q: Mutex<VecDeque<T>>, cv: Condvar, } impl<T> BlockingQueue<T> { fn new() -> Self { Self { q: Mutex::new(VecDeque::new()), cv: Condvar::new(), } } fn en_q(&self, t:T) { let mut lq = self.q.lock().unwrap(); lq.push_back(t); self.cv.notify_one(); } fn de_q(&self) -> T { let mut lq = self.q.lock().unwrap(); while lq.len() == 0 { lq = self.cv.wait(lq).unwrap(); } lq.pop_front().unwrap() } fn len(&self) -> usize { self.q.lock().unwrap().len() } } /*-- simple test of BlockingQueue --*/ fn test() { let share = Arc::new(BlockingQueue::<String>::new()); let share1 = Arc::clone(&share); let share2 = Arc::clone(&share); let flush = || { let _ = std::io::stdout().flush(); }; /*-- child thread dequeues messages --*/ let handle = thread::spawn(move || { print!("\n child thread started"); flush(); loop { let t = share1.de_q(); print!("\n dequeued {} on child thread", t); flush(); if &t == "quit" { break; } } print!("\n thread shutting down"); flush(); }); /*-- main thread enqueues messages --*/ for i in 0..5 { let msg = format!("msg #{}", i.to_string()); print!("\n enqueued {:?} on main thread", msg); flush(); share2.en_q(msg); } /*-- shut down child thread --*/ print!("\n enqueued {:?} on main thread", "quit"); flush(); share2.en_q("quit".to_string()); /*-- child thread must complete before exiting --*/ print!("\n waiting for child thread to stop"); flush(); let _ = handle.join(); print!("\n queue length = {}", share2.len()); } fn main() { print!("\n Demonstrate queue shared between threads"); print!("\n =========================================="); //demo(); test(); print!("\n\n That's all Folks!\n"); } Output: Demonstrate queue shared between threads ========================================== enqueued "msg #0" on main thread child thread started dequeued msg #0 on child thread enqueued "msg #1" on main thread enqueued "msg #2" on main thread dequeued msg #1 on child thread dequeued msg #2 on child thread enqueued "msg #3" on main thread enqueued "msg #4" on main thread enqueued "quit" on main thread waiting for child thread to stop dequeued msg #3 on child thread dequeued msg #4 on child thread dequeued quit on child thread thread shutting down queue length = 0 That's all Folks!
The code for these examples can be found in RustLibraryDemos.

5.9 mpsc - Message passing between threads

Reference: std::mpsc library. Structs: The std::mpsc types are: Receiver, Sender, SyncSender, IntoIter, Iter, TryIter, RecvError, and SendError
List of Structs
  1. Receiver retrieves messages from a channel can only be owned by one thread Traits: Debug, IntoIterator, Drop, Send, !Sync, ... pub fn try_recv(&self) -> Result<T, TryRecvError>non-blocking pub fn recv(&self) -> Result<T, RecvError>blocks on no data pub fn recv_timeout(&self, timeout: Duration) -> Result<T, RecvTimeoutError> pub fn iter(&self) -> Iter<T> pub fn try_iter(&self) -> TryIter<T>
  2. Sender sends messages to a a channel, non-blocking can only be owned by one thread, but can be cloned Traits: Debug, Clone, Send, !Sync, Drop, ... pub fn send(&self, t: T) -> Result<(), SendError>
  3. SyncSender sends messages to a channel, blocks if there is not enough space in internal buffer Traits: Debug, Clone, Send, Drop, ... pub fn send(&self, t: T) -> Result<(), SendError> pub fn try_send(&self, t: T) -> Result<(), TrySendError>
  4. IntoIter blocks when next is called, waiting for a new message Traits: Debug, Iterator, ...
  5. Iter blocks when next is called, waiting for a new message Traits: Debug, Iterator, ...
  6. TryIter attempts to yield all pending values for a Receiver Traits: Debug, Iterator, ...
  7. SendError sends only fail if receiving end is disconnected. error contains un-sent message as payload Traits: Debug, Display, Clone, Copy, Eq, PartialEq, ...
  8. ReceiveError receives only fail if sending end is disconnected. error contains un-sent message as payload Traits: Debug, Display, Clone, Copy, Eq, PartialEq, ...
mpsc functions are: channel and sync_channel
List of Functions
  1. pub fn channel<T>() -> (Sender<T>, Receiver<T>)
  2. pub fn sync_channel<T>(bound: usize) -> (SyncSender<T>, Receiver<T>)
std::mpsc enums are: RecvTimeoutError, TryRecvError, and TrySendError
List of Enumerations
  1. Traits: Debug, Display, Clone, Copy, Error, Eq, PartialEq, ....
  2. pub enum RecvTimeoutError { Timeout, Disconnected, }
  3. pub enum TryRecvError { Empty, Disconnected, }
  4. pub enum TrySendError<T>{ Full(T), Disconnected(T), }
The example below constructs a channel and sends messages between threads.
Example:  channel_demo std::mpsc - mpsc_demo::main.rs ///////////////////////////////////////////////////////////// // mpsc_demo::main.rs - msg passing uses blocking queue // // // // Jim Fawcett, https://JimFawcett.github.io, 22 May 2020 // ///////////////////////////////////////////////////////////// use std::sync::mpsc; use std::thread; use std::time::Duration; fn send_proc(s:&str, tx : &mpsc::Sender<String>) { let max = 5; for i in 0..max { let msg = format!("msg #{} from {}", i.to_string(), s); print!("\n sending {:?}", msg); tx.send(msg).unwrap(); thread::yield_now(); // others ready to run? } } fn recv_proc(rx: &mpsc::Receiver<String>) { for msg in rx { print!("\n received {:?}", msg); if msg == "quit" { break; } } } fn demo() { /*-- setup channel --*/ let (tx, rx) = mpsc::channel::<String>(); let tx2 = mpsc::Sender::clone(&tx); let tx_quit = mpsc::Sender::clone(&tx); /*-- receive thread --*/ let rcv_handle = thread::spawn( move || { recv_proc(&rx); }); /*-- wait for receive thread to start --*/ thread::sleep(Duration::from_millis(50)); /*-- send threads --*/ let toms_handle = thread::spawn( move || { send_proc("Tom", &tx); }); let jerrys_handle = thread::spawn( move || { send_proc("Jerry", &tx2); }); let _ = toms_handle.join(); let _ = jerrys_handle.join(); /*-- Tom and Jerry are finished so it's safe to stop receiver --*/ let _ = tx_quit.send("quit".to_string()); let _ = rcv_handle.join(); } fn main() { print!("\n Message passing demo"); print!("\n ======================"); demo(); print!("\n\n That's all Folks!\n"); } Output: Message passing demo ====================== sending "msg #0 from Tom" sending "msg #0 from Jerry" sending "msg #1 from Jerry" sending "msg #2 from Jerry" sending "msg #3 from Jerry" received "msg #0 from Tom" received "msg #0 from Jerry" sending "msg #4 from Jerry" sending "msg #1 from Tom" sending "msg #2 from Tom" received "msg #1 from Jerry" received "msg #2 from Jerry" received "msg #3 from Jerry" received "msg #4 from Jerry" received "msg #1 from Tom" received "msg #2 from Tom" sending "msg #3 from Tom" sending "msg #4 from Tom" received "msg #3 from Tom" received "msg #4 from Tom" received "quit" That's all Folks!
The code for this example can be found in RustLibraryDemos.

5.10 Async/Await - concurrent functions and blocks

std::future, crates.io/crates/futures, async/.await Primer This section is a place-holder, awaiting (pun intended) construction.

5.11 process - Creating child processes

Reference: std::process library. Structs: The std::thread types are: Command, Child, ChildStdin, ChildStdout, ChildStderr, Output, Stdio, ExitCode
List of Structs
  1. Command Traits: Debug, ... pub fn new<S: AsRef<OsStr>>(program: S) -> Command pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command pub fn args<I, S>(&mut self, args: I) -> &mut Command where I: IntoIterator<Item = S>, S: AsRef<OsStr>, pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command where K: AsRef<OsStr>, V: AsRef<OsStr>, pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Command where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>, pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Command pub fn env_clear(&mut self) -> &mut Command pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Command pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command pub fn spawn(&mut self) -> Result<Child> pub fn output(&mut self) -> Result<Output> pub fn status(&mut self) -> Result<ExitStatus>
  2. Child Traits: Debug, ... pub fn kill(&mut self) -> Result<()> pub fn id(&mut self) -> u32 pub fn wait(&mut self) -> Result<ExitStatus>
  3. ChildStdin Traits: Debug, From, Write, ...
  4. ChildStdout Traits: Debug, From, Read, ...
  5. ChildStderr Traits: Debug, From, Read, ...
  6. Stdio Traits: Debug, From, ... pub fn piped() -> Stdio pub fn inherit() -> Stdio pub fn null() -> Stdio
  7. Output Traits: Debug, Clone, Eq, PartialEq, ...
  8. ExitStatus Traits: Debug, Display, Copy, Clone, Eq, PartialEq, ... pub fn success(&self) -> bool pub fn code(&self) -> Option<i32>
  9. ...
Process functions are: abort, exit, id
List of Functions
  1. pub fn abort() -> !
  2. pub fn exit(code: i32) -> !
  3. pub fn id() -> u32
The example below spawns a child process and collects its output for display when the child exits.
Example:  process_demo std::thread::shared ///////////////////////////////////////////////////////////// // process_demo::main.rs - start child process // // // // Jim Fawcett, https://JimFawcett.github.io, 16 May 2020 // ///////////////////////////////////////////////////////////// #![allow(unused_imports)] use std::process::*; use std::io::*; fn main() { let putline = || print!("\n"); print!("\n spawning child process"); print!("\n ========================\n"); putline(); let output = if cfg!(target_os = "windows") { Command::new("cmd.exe") .args(&[ "/C", "type", "cargo.toml" ]) .output() .expect("failed to execute process") } else { Command::new("sh") .arg("-C") .arg("echo.hello") .output() .expect("failed to execute process") }; std::io::stdout().write_all( &output.stdout ).unwrap(); println!("\n\n That's all Folks!\n\n"); } Output: spawning child process ======================== [package] name = "process_demo" version = "0.1.0" authors = ["James W. Fawcett "] edition = "2018" # See more keys and their definitions at # https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] That's all Folks!
The code for this example can be found in RustLibraryDemos.

5.12 time - System time, time durations

Reference: std::time library. Structs: The std::time types are: Duration, Instant, SystemTime, SystemTimeError
List of Structs
  1. Duration Traits: Debug, Default, Clone, Copy, ... pub fn new(secs: u64, nanos: u32) -> Duration pub fn from_secs(secs: u64) -> Duration pub fn from_millis(millis: u64) -> Duration pub fn from_micros(micros: u64) -> Duration pub fn from_nanos(nanos: u64) -> Duration pub fn as_secs(&self) -> u64 pub fn as_millis(&self) -> u128 pub fn as_micros(&self) -> u128 pub fn as_nanos(&self) -> u128 pub fn checked_add(self, rhs: Duration) -> Option<Duration> pub fn checked_sub(self, rhs: Duration) -> Option<Duration> pub fn checked_mul(self, rhs: u32) -> Option<Duration> pub fn checked_div(self, rhs: u32) -> Option<Duration> s...
  2. Instant Traits: Debug, Clone, Copy, ... pub fn now() -> Instant pub fn duration_since(&mut self, earlier: Instant) -> Duration pub fn checked_duration_since(&mut self, earlier: Instant) -> Option<Duration> pub fn elapsed(&mut self) -> Duration ...
  3. SystemTime Traits: Debug, Clone, Copy, Eq, PartialEq, ... pub const UNIX_EPOCH: SystemTime pub fn now() -> SystemTime pub fn duration_since(&self, earlier: SystemTime) -> Result<Duration, SystemTimeError> pub fn elapsed(&self) -> Result<Duration, SystemTimeError> ...
  4. SystemTimeError Traits: Debug, Display, Clone, Error, ... Traits: Debug, Display, Clone, Error, ... pub fn duration(&self) -> Duration
The example below may eventually do something interesting.
Example:  time_demo std::time time_demo ///////////////////////////////////////////////////////////// // time_demo::main.rs - demonstration of std::time // // // // Jim Fawcett, https://JimFawcett.github.io, 22 May 2020 // ///////////////////////////////////////////////////////////// use std::time; use std::thread; use std::convert::TryFrom; /*-- how accurate is thread sleep ? --*/ fn sleep_proc(ms: u64) { let dur = time::Duration::from_millis(ms); thread::sleep(dur); } /*-- nonsense calculation to waste time --*/ fn calc_proc(count: u64) { let mut _value: f64 = 42.0; let mut mult:i32 = 42; let rslt = i32::try_from(count); match rslt { Ok(counter) => { for i in 0..counter { mult = mult + i; let mult = (mult + i) % 33; let _value = _value + mult as f64; } }, Err(_) => print!("\n can't compute this number") } } /*-- writing to console is slow --*/ fn print_proc(count: u64) { print!("\n "); for _num in 0..count { for _out in 0..25 { print!(". "); } print!("\n "); } } /*-- stop watch timer --*/ fn measure_time<F: FnOnce(u64) -> ()>(f: F, arg: u64) -> time::Duration { let start = time::Instant::now(); f(arg); start.elapsed() } /*-- demo time durations --*/ fn main() { print!("\n Demonstrate std::time"); print!("\n ======================="); let putline = || print!("\n"); putline(); let mut duration = measure_time(sleep_proc, 3); print!("\n thread::sleep for 3 ms actually took {:?}", duration); putline(); duration = measure_time(calc_proc, 1500); print!("\n time to perform nonsense calc was {:?}", duration); putline(); duration = measure_time(print_proc, 15); print!("\n time to print array of dots was {:?}", duration); println!("\n\n That's all Folks!\n"); } Output: Demonstrate std::time ======================= thread::sleep for 3 ms actually took 3.6934ms time to perform nonsense calc was 62µs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . time to print array of dots was 11.503ms That's all Folks!
The code for this example can be found in RustLibraryDemos.

5.13 Date - Date and time stamps

placeholder awaiting construction

5.14 Epilogue

Augmenting the standard Rust libraries, there are a large number of open-source crates on https://crates.io. Many are developed by members of the Rust development team. There are also a lot of resources available on github. Here are a few interesting selections:
  1. hyper, an HTTP client and server api
  2. http-rs, HTTP client, server, and parts.
  3. async-std, an implementation of the async-await pattern and updates of some of the std libraries to incorporate it, e.g., fs, io, net, process, sync, ... It provides new libraries future, stream, and task. Here's a nice set of examples of its use.
  4. tokio, a widely used runtime for asynchronous applications.
  5. serde, framework for serializing and deserializing Rust data structures.
  6. awesome-rust, a large list of rust resources, mostly on github.
I've been impressed with Rust's clever ideas, professional implementation, and ease of use of the Rust tool chain. Cheers!

5.15 References

Reference Link Description
A Gentle Introduction To Rust First thing to read.
The Rust Book Rust docs - walkthrough of syntax
The Rust Reference Book Rust's approximation of a language standard. Clear and well written, with a glossary at the end. Its github site shows that changes are still being actively incorporated.
Rust cheat sheet Quite extensive list of cheats and helpers.
Rust Containers Container diagrams
Rust IPC Stackoverflow re Rust IPC
Rust file io Stackoverflow re Rust file io
Rust metadata Stackoverflow re reading metadata from cargo.toml
RIP Tutorial on Rust Comprehensive coverage from RIP, the stackoverflow archive
Learning Rust ebook Comprehensive coverage from RIP, stackoverflow archive
Rust - Awesome Book lots of interesting discussions from RIP, the Stackoverflow Archive
std::regex std docs on Regex
Shared reference &T and exclusive reference &mut T More accurate description than immutable reference and mutable reference
Blog - Pascal's Scribbles Pascal Hertleif - Rust contributor
Blog - Barely Functional Michael Gattozzi - Rust contributor
Rust and the case for WebAssembly in 2018 Michael Gattozzi - Rust contributor
Blog - S.Noyberg Examples of aysnc/await, tokio, lifetime, ...
More References  
  Next Prev Pages Sections About Keys