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.
-
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;
-
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
-
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:
Collection | Description |
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:
-
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
...
}
-
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
-
fn next(&mut self) -> Option<Self::Item>
Advances and returns value.
-
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.
-
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.
-
fn take(self, n:usize) -> Take<Self>
-
fn take_while<P>(self, predicate: P) -> TakeWhile<Self, P>
where P: FnMut(&Self::Item) -> bool
-
fn fold<B, F>(self, init:B, f:F) -> B
where FnMut(B, Self::Item) -> B
-
fn skip(self, n:usize) -> Skip<Self>
-
fn collect<B>(self) -> B
where B: FromIterator<Self::Item>
-
step_by(self, step:usize) -> StepBy<Self>
Skips step items on each iteration
-
nth(&mut self, n:usize) -> Option<Self::item>
Returns nth item, counting from zero
-
last(self) -> Option<Self::item>
Consumes iterator, returning last item
-
fn find<P>(&mut self, predicate: P) -> Option<Self::Item>
where FnMut(&Self::Item) -> bool
-
fn position<P>(&mut self, predicate: P) -> Option<usize>
where P: FnMut(Self::Item) -> bool
-
fn for_each<F>(self, f:F)
where F: FnMut(Self::Item)
Calls a closure on each element of an iterator.
-
fn enumerate(self) -> Enumerate<Self>
Creates an iterator returns a pair with current iteration count and next value.
-
fn by_ref(&mut self) -> &mut Self
-
fn all<F>(&mut self, f:F) -> bool
where F: FnMut(Self::Item) -> bool
-
fn any<F>(&mut self, f:F) -> bool
where F: FnMut(Self::Item) -> bool
-
fn count(self) -> usize
Consumes iterator, counting number of iterations
-
fn chain<U>(self, other: U) -> Chain<Self, <U as IntoIterator>::IntoIter>
where U: IntoIterator<Self::Item>
-
fn zip<U>(self, other: U) -> Zip<Self, <U as IntoIterator>::IntoIter>
where U: IntoIterator
-
fn sum<S>(self) -> S
where S: Sum<Self::Item>
-
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
-
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,
...
}
-
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
...
}
-
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<()>,
...
}
-
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
-
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
-
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>>>
-
Bytes
Implements Traits Debug, Iterator ...
-
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
-
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>>>
-
Lines
Implements Traits Debug, Iterator ...
-
Stdin
Implements Traits Debug, Read ...
pub fn lock(&self) -> StdinLock
pub fn read_line(&self, buf: &mut String) -> Result<usize>
-
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
-
pub fn copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> Result<u64>
-
pub fn stderr() -> Stderr
-
pub fn stdin() -> Stdin
-
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
- 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
- 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
- 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<()>
- FileType
Implements Traits Clone, Copy, Debug, ...
pub fn is_dir(&mut self) -> bool
pub fn is_file(&mut self) -> bool
- 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>
- 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>
- Permissions
Implements Traits Clone, Debug, Eq, PartialEq, ...
pub fn readonly(&self) -> bool
pub fn set_readonly(&mut self, readonly: bool)
- 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
-
fn canonicalize<P: AsRef<Path>>(path: P) -> Result<PathBuf>
-
fn create_dir<P: AsRef<Path>>(path: P) -> Result<()>
-
fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir>
-
fn remove_dir<P: AsRef<Path>>(path: P) -> Result<()>
-
fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata>
-
fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>>
-
fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir>
-
fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String>
-
fn write<P: AsRef<Path>>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
-
fn remove_file<P: AsRef<Path>>(path: P) -> Result<()>
-
fn rename<P: AsRef<Path>>(path: P) -> Result<()>
-
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
- 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<()>
...
- 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<()>
...
- 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<()>
...
- 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
- 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
- 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)
- 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)
- AddrParseError
Traits: Error, Debug, Display, Clone, Eq, PartialEq, ...
Enums:
The net library has enums: IpAddr, SocketAddr, Shutdown, and Ipv6MulitcastScope.
List of net Enums
-
pub enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr), }
-
pub enum SocketAddr { V4(SocketAddrV4), V6(SocketAddrV6), }
-
pub enum Shutdown { Read, Write, Both, }
-
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:
-
trait Send { }
// marker trait
// - types that can be transferred across thread boundaries
-
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
- Thread
Traits: Debug, Clone, Send, Sync, ...
pub fn id(&self) -> ThreadId
pub fn name(&self) -> Option<&str>
- ThreadId
Traits: Debug, Copy, Clone, Eq, PartialEq, Send, Sync, ...
- 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'
- JoinHandle
Traits: Debug, Send, Sync, ...
pub fn thread(&self) -> &Thread
pub fn join(self) -> Result<T>
- LocalKey
Traits: Debug, Send, Sync, ...
pub fn with<F, R>(&'static self, f: F) -> R
where F: FnOnce(&T) -> R
...
- AccessError
Traits: Debug, Display, Clone, Copy, Eq, PartialEq, Error, Send, Sync, ...
Thread functions are: spawn, current, sleep, yield_now, ...
List of Functions
-
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static
-
pub fn current() -> Thread
-
pub fn sleep(dur: std::time::Duration)
-
pub fn yield_now()
-
...
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
- 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
- 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>
...
- MutexGuard
Traits: Debug, Default, Send, Sync, ...
- 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)
- RwLock
Traits: rDebug, Default, From, Send, Sync, ...
pub fn new(t: T) -> RwLock<T>
pub fn read(&self) -> LockResult<RwLockReadGuard<T>>
- RwLockReadGuard
Traits: Debug, Display, Deref, !Send, Sync, ...
- RwLockWriteGuard
Traits: Debug, Display, Deref, DerefMut, !Send, Sync, ...
- 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
- WaitTimeoutResult
Traits: Debug, Copy, Clone, Eq, PartialEq, Send, Sync, ...
pub fn timed_out(&self) -> bool
- 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
- Barrier
Traits: Debug, Send, Sync, ...
pub fn new(n: usize) -> Barrier
pub fn wait(&self) -> BarrierWaitResult
- BarrierWaitResult
Traits: Debug, Send, Sync, ...
-
...
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
- 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>
- 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>
- 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>
- IntoIter
blocks when next is called, waiting for a new message
Traits: Debug, Iterator, ...
- Iter
blocks when next is called, waiting for a new message
Traits: Debug, Iterator, ...
- TryIter
attempts to yield all pending values for a Receiver
Traits: Debug, Iterator, ...
- SendError
sends only fail if receiving end is disconnected.
error contains un-sent message as payload
Traits: Debug, Display, Clone, Copy, Eq, PartialEq, ...
- 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
-
pub fn channel<T>() -> (Sender<T>, Receiver<T>)
-
pub fn sync_channel<T>(bound: usize) -> (SyncSender<T>, Receiver<T>)
std::mpsc enums are: RecvTimeoutError, TryRecvError, and TrySendError
List of Enumerations
-
Traits: Debug, Display, Clone, Copy, Error, Eq, PartialEq, ....
-
pub enum RecvTimeoutError { Timeout, Disconnected, }
-
pub enum TryRecvError { Empty, Disconnected, }
-
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
- 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>
- Child
Traits: Debug, ...
pub fn kill(&mut self) -> Result<()>
pub fn id(&mut self) -> u32
pub fn wait(&mut self) -> Result<ExitStatus>
- ChildStdin
Traits: Debug, From, Write, ...
- ChildStdout
Traits: Debug, From, Read, ...
- ChildStderr
Traits: Debug, From, Read, ...
- Stdio
Traits: Debug, From, ...
pub fn piped() -> Stdio
pub fn inherit() -> Stdio
pub fn null() -> Stdio
- Output
Traits: Debug, Clone, Eq, PartialEq, ...
- ExitStatus
Traits: Debug, Display, Copy, Clone, Eq, PartialEq, ...
pub fn success(&self) -> bool
pub fn code(&self) -> Option<i32>
-
...
Process functions are: abort, exit, id
List of Functions
-
pub fn abort() -> !
-
pub fn exit(code: i32) -> !
-
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
- 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...
- 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
...
- 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>
...
- 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:
-
hyper, an HTTP client and server api
-
http-rs, HTTP client, server, and parts.
-
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.
-
tokio, a widely used runtime for asynchronous applications.
-
serde, framework for serializing and deserializing
Rust data structures.
-
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