Import wyz 0.5.0 upstream upstream/0.5.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Fri, 24 Mar 2023 01:35:59 +0000 (10:35 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Fri, 24 Mar 2023 01:35:59 +0000 (10:35 +0900)
21 files changed:
.editorconfig [new file with mode: 0644]
.gitignore [new file with mode: 0644]
AUTHORS.txt [new file with mode: 0644]
CHANGELOG.md [new file with mode: 0644]
CODE_OF_CONDUCT.md [new file with mode: 0644]
CONTRIBUTING.md [new file with mode: 0644]
Cargo.toml [new file with mode: 0755]
Justfile [new file with mode: 0644]
LICENSE.txt [new file with mode: 0644]
README.md [new file with mode: 0644]
rust-toolchain.toml [new file with mode: 0644]
rustfmt-stable.toml [new file with mode: 0644]
rustfmt.toml [new file with mode: 0644]
src/bidi.rs [new file with mode: 0644]
src/comu.rs [new file with mode: 0644]
src/exit.rs [new file with mode: 0644]
src/fmt.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/range.rs [new file with mode: 0644]
src/wm.rs [new file with mode: 0644]
tarpaulin.toml [new file with mode: 0644]

diff --git a/.editorconfig b/.editorconfig
new file mode 100644 (file)
index 0000000..3172c56
--- /dev/null
@@ -0,0 +1,28 @@
+################################################################################
+#                             Editor Configuration                             #
+#                                                                              #
+# This file controls behavior in conformant editors with respect to some       #
+# common baseline settings.                                                    #
+################################################################################
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[Justfile]
+indent_size = 8
+indent_style = tab
+
+[*.md]
+indent_size = 2
+indent_style = space
+
+[*.rs]
+indent_size = 4
+indent_style = tab
+
+[*.toml]
+indent_size = 8
+indent_style = tab
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..6936990
--- /dev/null
@@ -0,0 +1,3 @@
+/target
+**/*.rs.bk
+Cargo.lock
diff --git a/AUTHORS.txt b/AUTHORS.txt
new file mode 100644 (file)
index 0000000..3d46739
--- /dev/null
@@ -0,0 +1 @@
+myrrlyn <self@myrrlyn.dev>
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644 (file)
index 0000000..8d125e2
--- /dev/null
@@ -0,0 +1,67 @@
+# Changelog <!-- omit in toc -->
+
+All notable changes will be documented in this file.
+
+This document is written according to the [Keep a Changelog][kac] style.
+
+## 0
+
+### 0.5.0
+
+Added the `Bidi` iterator adapter, which applies a `.rev()` on construction if a
+given condition is true.
+
+Added the `RangeExt` trait for making some operations on
+`<R: RangeBounds<usize>>` easier.
+
+Added a `FmtList` type (and `.fmt_list()` method) which allows anything that can
+be borrowed as an iterator to render itself conveniently.
+
+Added more pointer methods to `Address`, and created a system for working with
+references as well as pointers.
+
+### 0.4.0
+
+Add the `comu` module containing the type-system mutability tracking extracted
+from `bitvec`.
+
+### 0.3.0
+
+Added a background garbage disposal system in the `wm` module, under the
+`garbage` feature. It is accessed by importing the `wm::BgDropExt` trait and
+using its `.bg_drop()` method on a value.
+
+The disposal system manages a single worker thread which receives any type and
+runs the appropriate destructor for it. Once initialized, the system remains in
+operation until explicitly shut down by the client program; once shut down, all
+future deferred-drop objects are destroyed in their local thread as normal.
+
+This system allows programs to opt in to faster immediate behavior when a value
+goes out of scope, with minimal system and per-value cost.
+
+Removed `tap`, `pipe`, and `conv`. They have been promoted to the [`tap`] crate.
+
+### 0.2.0
+
+Added `conv::TryConv` for fallible directed conversion.
+
+Added `fmt` module, which supplies behavior to forward any formatting trait to
+`Debug`.
+
+Removed `pretty` module in favor of `fmt`.
+
+### 0.1.1
+
+Fix typos.
+
+### 0.1.0
+
+Initial release, featuring:
+
+- `conv`
+- `exit`
+- `pipe`
+- `pretty`
+- `tap`
+
+[`tap`]: https://crates.io/crates/tap
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644 (file)
index 0000000..7cf643e
--- /dev/null
@@ -0,0 +1,8 @@
+# Code of Conduct
+
+This project is subject to the official [Rust code of conduct][coc].
+
+As there are no dedicated fora for this project, this is only relevant in the
+repository or in communication with me about it.
+
+[coc]: https://www.rust-lang.org/policies/code-of-conduct
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..12ce02b
--- /dev/null
@@ -0,0 +1,45 @@
+# Contributing Guide
+
+Contributions are absolutely welcome!
+
+## Contact Information
+
+In order of likelihood that I will actionably receive your contact, my
+information is:
+
+- Email: [self@myrrlyn.dev](mailto:self@myrrlyn.dev)
+- GitHub: [@myrrlyn](//github.com/myrrlyn)
+- Twitter: [@myrrlyn](//twitter.com/myrrlyn)
+- Mastodon: [@myrrlyn@cybre.space](//cybre.space/myrrlyn)
+- Reddit: [/u/myrrlyn](//reddit.com/u/myrrlyn)
+
+I am not active on any IRC channels at this time. I am on Discord in the Rust
+channel, so you may be able to reach me there, but I don’t know offhand how to
+give out Discord profile links. I have a very consistent username scheme and so
+anywhere you see my name, it’s *probably* me and I’ll *probably* respond to it.
+
+## Preconditions
+
+Be able to make a Rust project compile.
+
+Be comfortable using `U+0009 CHARACTER TABULATION` as your indentation setting.
+
+## Contributing
+
+If you have a patch you think is worth inspecting right away, opening a pull
+request without prelude is fine, although I would certainly appreciate an
+accompanying explanation of what the patch does and why.
+
+If you have questions, bugs, suggestions, or other contributions of any kind
+that do not immediately touch the codebase, you can reach me informally to talk
+about them or open an issue.
+
+I will do my best to respond to all contacts in a timely manner.
+
+## Workflow
+
+This project uses a `Justfile` to contain its workflows. You can install the
+`just` tool from Cargo (`cargo install just`), or from any of the sources listed
+at https://github.com/casey/just. If you run `just loop dev` in a separate
+terminal while you work, or run `just dev` as your editor’s on-save event, you
+should be all set.
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100755 (executable)
index 0000000..fc624ce
--- /dev/null
@@ -0,0 +1,54 @@
+################################################################################
+#                               Project Manifest                               #
+#                                                                              #
+# This file describes the Rust project to the Cargo build tool for operations. #
+################################################################################
+
+[package]
+name = "wyz"
+version = "0.5.0"
+authors = [
+       "myrrlyn <self@myrrlyn.dev>",
+]
+edition = "2018"
+categories = [
+       "no-std",
+]
+description = "myrrlyn’s utility collection"
+documentation = "https://docs.rs/wyz"
+homepage = "https://myrrlyn.net/crates/wyz"
+include = [
+       "Cargo.toml",
+       "LICENSE.txt",
+       "README.md",
+       "src/**/*.rs",
+]
+keywords = [
+]
+license = "MIT"
+readme = "README.md"
+repository = "https://github.com/myrrlyn/wyz"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[dependencies.once_cell]
+version = "1"
+optional = true
+
+[dependencies.tap]
+version = "1"
+
+[dependencies.typemap]
+version = "0.3"
+optional = true
+
+[features]
+alloc = []
+garbage = [
+       "once_cell",
+       "typemap",
+]
+default = ["std"]
+std = ["alloc"]
diff --git a/Justfile b/Justfile
new file mode 100644 (file)
index 0000000..9d2c22c
--- /dev/null
+++ b/Justfile
@@ -0,0 +1,57 @@
+################################################################################
+#                                   Justfile                                   #
+#                                                                              #
+# Set of routines to execute for development work.                             #
+################################################################################
+
+# Run the benchmarks. Currently, this requires the nightly compiler series.
+bench:
+       cargo +nightly bench
+
+# Build the project, after checking that it is valid.
+build: check
+       cargo build --all-features
+
+# Runs the checker and linter.
+check: format
+       cargo check --all-features
+       cargo clippy --all-features
+
+# Destroys build artifacts.
+clean:
+       cargo clean
+
+# Development workflow.
+dev: format check doc test
+
+# Documents the project, after checking that it is valid.
+doc: check
+       cargo doc --document-private-items --all-features
+
+format:
+       cargo +nightly fmt
+
+# Runs a Justfile recipe on every change to the workspace.
+loop action:
+       watchexec -- "just {{action}}"
+
+# Runs the project under the Miri interpreter. This is currently nightly-only.
+miri:
+       cargo +nightly miri test
+
+# Prepares the project for package deployment.
+#
+# This allows uncommitted VCS files, as a convenience for development.
+package: test doc
+       cargo package --allow-dirty
+
+# Publishes the project to crates.io.
+#
+# This repackages the project and fails on a dirty VCS checkout.
+publish: test doc
+       cargo package # no --allow-dirty this time
+       cargo publish
+
+# Runs the test suite.
+test: build
+       cargo test --all-features
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644 (file)
index 0000000..467fbd9
--- /dev/null
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 myrrlyn (Alexander Payne)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..18af4d3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,192 @@
+<div align="center">
+
+# `wyz` <!-- omit in toc -->
+
+[![Crate][crate_img]][crate]
+[![Documentation][docs_img]][docs]
+[![License][license_img]][license_file]
+
+[![Continuous Integration][travis_img]][travis]
+[![Code Coverage][codecov_img]][codecov]
+[![Crate Downloads][downloads_img]][crate]
+[![Crate Size][loc_img]][loc]
+
+</div>
+
+I have developed a collection of utility and convenience Rust modules that are
+useful to me, and may be useful to you also.
+
+This crate is a collection of largely-independent small modules. I do not
+currently offer features to disable modules independently of each other, but
+their compilation cost is small enough to essentially not matter.
+
+## Modules <!-- omit in toc -->
+
+1. [`conv`](#conv)
+1. [`exit`](#exit)
+1. [`fmt`](#fmt)
+1. [`pipe`](#pipe)
+1. [`tap`](#tap)
+
+## `conv`
+
+This module provides a single trait, of the same name, with a single generic
+method, also of the same name. This trait is a sibling to `Into`, but rather
+than placing its type parameter in the trait (`Into::<T>::into`), `Conv` places
+it in the method: `Conv::conv::<T>`.
+
+By placing the type parameter in the method name, `.conv` can be called in
+suffix position in expressions where the result type cannot be inferred and must
+be explicitly stated.
+
+```rust
+use wyz::conv::Conv;
+
+let digits = 0xabcd.conv::<String>().len();
+```
+
+This is a trivial example, but writing a code context where `.conv` makes sense
+takes a lot more context than a `README` wants.
+
+## `exit`
+
+This is a macro that calls `std::process::exit`. It can return a status code,
+and also print a message to `stderr`.
+
+```rust
+use wyz::exit::exit;
+
+exit!();
+exit!(2);
+exit!(3, "This is a {} message", "failure");
+```
+
+The default call is `std::process::exit(1)`; a call may provide its own exit
+code and, in addition, a set of arguments to pass directly to `eprintln!`. The
+error message is not guaranteed to be emitted, as `stderr` may be closed at time
+of `exit!`.
+
+## `fmt`
+
+Rust uses the `Debug` trait for automatic printing events in several parts of
+the standard library. This module provides wrapper types which forward their
+`Debug` implementation to a specified other formatting trait. It also implements
+extension methods on all types that have format trait implementations to wrap
+them in the corresponding shim type.
+
+```rust
+use wyz::fmt::FmtForward as _;
+
+let val = 6;
+let addr = &val as *const i32;
+println!("{:?}", addr.fmt_pointer());
+```
+
+This snippet uses the `Debug` format template, but will print the `Pointer`
+implementation of `*const i32`.
+
+This is useful for fitting your values into an error-handling framework that
+only uses `Debug`, such as the `fn main() -> Result` program layout.
+
+## `pipe`
+
+Rust does not permit universal suffix-position function call syntax. That is,
+you can *always* call a function with `Scope::name(arguments…)`, but only *some*
+functions can be called as `first_arg.name(other_args…)`. Working in “data
+pipelines” – flows where the return value of one function is passed directly as
+the first argument to the next – is common enough in our field that it has a
+name in languages that support it: *method chaining*. A *method* is a function
+that the language considers to be treated specially in regards to only its
+first argument, and permits changing the abstract token series
+`function arg1 args…` into the series `arg1 function args…`.
+
+Rust restricts that order transformation to only functions defined in scope for
+some type (either `impl Type` or `impl Trait for Type` blocks) and that take a
+first argument named `self`.
+
+Other languages permit calling *any* function, regardless of its definition site
+in source code, in this manner, as long as the first argument is of the correct
+type for the first parameter of the function.
+
+In languages like F♯ and Elixir, this uses the call operator `|>` rather than
+the C++ family’s `.` caller. This operator is pronounced `pipe`.
+
+Rust does not have a pipe operator. The dot-caller is restricted to only the
+implementation blocks listed above, and this is not likely to change because it
+also performs limited type transformation operations in order to find a name
+that fits.
+
+This module provides a `Pipe` trait whose method, `pipe`, passes its `self`
+first argument as the argument to its second-order function:
+
+```rust
+use wyz::pipe::Pipe;
+
+let final = 5
+  .pipe(|a| a + 10)
+  .pipe(|a| a * 2);
+
+assert_eq!(final, 30);
+```
+
+Without language-level syntax support, piping into closures always requires
+restating the argument, and functions cannot curry the argument they receive
+from `pipe` and arguments from the environment in the manner that dot-called
+methods can.
+
+```rust
+fn fma(a: i32, b: i32, c: i32) -> i32 {
+  (a * b) + c
+}
+5.pipe(|a| fma(a, 2, 3));
+
+let fma_2_3 = |a| fma(a, 2, 3);
+5.pipe(fma_2_3);
+```
+
+These are the only ways to express `5 |> fma(2, 3)`.
+
+Sorry.
+
+Bug the language team.
+
+## `tap`
+
+Tapping is a cousin operation to piping, except that rather than pass the
+receiver by *value* into some function, and return the result of that function,
+it passes a *borrow* of a value into a function, and then returns the original
+value.
+
+It is useful for inserting an operation into an expression without changing the
+overall state (type or value) of the expression.
+
+```rust
+use wyz::tap::Tap;
+
+let result = complex_value()
+  .tap(|v| log::info!("First stage: {}", v))
+  .transform(other, args)
+  .tap(|v| log::info!("Second stage: {}", v));
+```
+
+The tap calls have no effect on the expression into which they are placed,
+except to induce side effects somewhere else in the system. Commenting out the
+two `.tap` calls does not change anything about `complex_value()`, `.transform`,
+or `result`; it only removes the log statements.
+
+This enables easily inserting or removing inspection points in an expression
+without otherwise altering it.
+
+[codecov]: https://codecov.io/gh/myrrlyn/wyz "Code Coverage"
+[codecov_img]: https://img.shields.io/codecov/c/github/myrrlyn/wyz.svg?logo=codecov "Code Coverage Display"
+[crate]: https://crates.io/crates/wyz "Crate Link"
+[crate_img]: https://img.shields.io/crates/v/wyz.svg?logo=rust "Crate Page"
+[docs]: https://docs.rs/wyz "Documentation"
+[docs_img]: https://docs.rs/wyz/badge.svg "Documentation Display"
+[downloads_img]: https://img.shields.io/crates/dv/wyz.svg?logo=rust "Crate Downloads"
+[license_file]: https://github.com/myrrlyn/wyz/blob/master/LICENSE.txt "License File"
+[license_img]: https://img.shields.io/crates/l/wyz.svg "License Display"
+[loc]: https://github.com/myrrlyn/wyz "Repository"
+[loc_img]: https://tokei.rs/b1/github/myrrlyn/wyz?category=code "Repository Size"
+[travis]: https://travis-ci.org/myrrlyn/wyz "Travis CI"
+[travis_img]: https://img.shields.io/travis/myrrlyn/wyz.svg?logo=travis "Travis CI Display"
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644 (file)
index 0000000..292fe49
--- /dev/null
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "stable"
diff --git a/rustfmt-stable.toml b/rustfmt-stable.toml
new file mode 100644 (file)
index 0000000..391d403
--- /dev/null
@@ -0,0 +1,23 @@
+################################################################################
+#                           Rust Style Configuration                           #
+#                                                                              #
+# This file controls the operation of `rustfmt` and `cargo fmt`. As the        #
+# `rustfmt` tool is still unstable, this file only contains the configurations #
+# that are stable as of the pinned Rust version in `rust-toolchain`. The file  #
+# `rustfmt-nightly.toml` contains the configurations that are available as of  #
+# the nightly Rust release when that file was last touched.                    #
+################################################################################
+
+edition = "2018"
+hard_tabs = true
+force_explicit_abi = true
+max_width = 81
+merge_derives = true
+newline_style = "Unix"
+remove_nested_parens = true
+reorder_imports = true
+reorder_modules = true
+tab_spaces = 4
+use_field_init_shorthand = true
+use_small_heuristics = "Default"
+use_try_shorthand = true
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644 (file)
index 0000000..733fa9b
--- /dev/null
@@ -0,0 +1,77 @@
+################################################################################
+#                           Rust Style Configuration                           #
+#                                                                              #
+# This file controls the operation of `rustfmt` and `cargo fmt`. As the        #
+# `rustfmt` tool is still unstable, this file is only usable by the nightly    #
+# release of Rust at least as of the date this file was last touched.          #
+#                                                                              #
+# See https://github.com/rust-lang/rustfmt/blob/master/Configurations.md for a #
+# list of all configuration options.                                           #
+################################################################################
+
+# Stable as of current nightly
+edition = "2018"
+fn_args_layout = "Tall"
+force_explicit_abi = true
+hard_tabs = true
+max_width = 81
+merge_derives = true
+newline_style = "Unix"
+print_misformatted_file_names = false
+remove_nested_parens = true
+reorder_imports = true
+reorder_modules = true
+tab_spaces = 4
+use_field_init_shorthand = true
+use_small_heuristics = "Default"
+use_try_shorthand = true
+
+# Still unstable
+binop_separator = "Front"
+blank_lines_lower_bound = 0
+blank_lines_upper_bound = 1
+brace_style = "SameLineWhere"
+color = "Auto"
+combine_control_expr = true
+comment_width = 80
+condense_wildcard_suffixes = true
+control_brace_style = "ClosingNextLine"
+disable_all_formatting = false
+empty_item_single_line = false
+enum_discrim_align_threshold = 40
+error_on_line_overflow = false
+error_on_unformatted = false
+fn_single_line = false
+force_multiline_blocks = false
+format_code_in_doc_comments = false
+format_macro_matchers = true
+format_macro_bodies = true
+format_strings = true
+hide_parse_errors = false
+ignore = []
+imports_indent = "Block"
+imports_layout = "Vertical"
+indent_style = "Block"
+inline_attribute_width = 0
+license_template_path = ""
+match_arm_blocks = true
+match_block_trailing_comma = true
+merge_imports = true
+normalize_comments = false
+normalize_doc_attributes = true
+overflow_delimited_expr = true
+reorder_impl_items = true
+report_fixme = "Unnumbered"
+report_todo = "Unnumbered"
+space_after_colon = true
+space_before_colon = false
+spaces_around_ranges = true
+struct_field_align_threshold = 0
+struct_lit_single_line = true
+trailing_comma = "Vertical"
+trailing_semicolon = true
+type_punctuation_density = "Wide"
+unstable_features = true
+version = "Two"
+where_single_line = true
+wrap_comments = true
diff --git a/src/bidi.rs b/src/bidi.rs
new file mode 100644 (file)
index 0000000..78ffbd9
--- /dev/null
@@ -0,0 +1,216 @@
+//! A bidirectional iterator that only checks its direction once.
+
+use core::iter::FusedIterator;
+
+/** An iterator that conditionally reverses itself upon creation.
+
+This acts as a conditional `.rev()` adapter: it reverses the direction of
+iteration, swapping `.next()` and `.next_back()`, but only if the provided
+condition is true. If the condition is false, then iteration proceeds normally.
+
+The condition is only evaluated when the adapter is constructed, and all calls
+to drive the iterator are branchless.
+
+## Usage
+
+This can be constructed directly with `Bidi::new(some_iterator)`, but it is more
+conveniently accessed as an extension method on double-ended iterators. Import
+`wyz::BidiIterator` or `wyz::bidi::*;` and then call `.bidi()` in your iterator
+adapter sequence.
+
+## Examples
+
+This can be used to hand-roll a `memmove` implementation that correctly handles
+the case where the destination begins in the source region:
+
+```rust
+use wyz::bidi::*;
+
+unsafe fn memmove<T>(from: *const T, to: *mut T, count: usize) {
+ let src = from .. from.add(count);
+ let rev = src.contains(&(to as *const T));
+ for idx in (0 .. count).bidi(rev) {
+  to.add(idx).write(from.add(idx).read());
+ }
+}
+```
+
+This example detects if `to` is between `from` and `from.add(count)` and uses
+that to determine whether to iterate forward from `0` to `count - 1` or backward
+from `count - 1` to `0`.
+**/
+pub struct Bidi<I>
+where I: DoubleEndedIterator
+{
+       /// The iterator being governed.
+       inner: I,
+       /// A pointer to either `I::next` or `I::next_back`.
+       next: fn(&mut I) -> Option<<I as Iterator>::Item>,
+       /// A pointer to either `I::next_back` or `I::next`.
+       next_back: fn(&mut I) -> Option<<I as Iterator>::Item>,
+       /// A pointer to either `I::nth` or `I::nth_back`.
+       nth: fn(&mut I, usize) -> Option<<I as Iterator>::Item>,
+       /// A pointer to either `I::nth_back` or `I::nth`.
+       nth_back: fn(&mut I, usize) -> Option<<I as Iterator>::Item>,
+}
+
+impl<I> Bidi<I>
+where I: DoubleEndedIterator
+{
+       /// Applies the `Bidi` adapter to a double-ended iterator and selects the
+       /// direction of traversal.
+       ///
+       /// ## Parameters
+       ///
+       /// - `iter`: anything that can be made into a double-ended iterator
+       /// - `cond`: determines whether iteration proceeds ordinarily or reversed
+       pub fn new<II>(iter: II, cond: bool) -> Self
+       where II: IntoIterator<IntoIter = I> {
+               let inner = iter.into_iter();
+               if cond {
+                       Self {
+                               inner,
+                               next: <I as DoubleEndedIterator>::next_back,
+                               next_back: <I as Iterator>::next,
+                               nth: <I as DoubleEndedIterator>::nth_back,
+                               nth_back: <I as Iterator>::nth,
+                       }
+               }
+               else {
+                       Self {
+                               inner,
+                               next: <I as Iterator>::next,
+                               next_back: <I as DoubleEndedIterator>::next_back,
+                               nth: <I as Iterator>::nth,
+                               nth_back: <I as DoubleEndedIterator>::nth_back,
+                       }
+               }
+       }
+}
+
+impl<I> Iterator for Bidi<I>
+where I: DoubleEndedIterator
+{
+       type Item = <I as Iterator>::Item;
+
+       #[inline]
+       fn next(&mut self) -> Option<Self::Item> {
+               (&mut self.next)(&mut self.inner)
+       }
+
+       #[inline]
+       fn nth(&mut self, n: usize) -> Option<Self::Item> {
+               (&mut self.nth)(&mut self.inner, n)
+       }
+
+       #[inline]
+       #[cfg(not(tarpaulin_include))]
+       fn size_hint(&self) -> (usize, Option<usize>) {
+               self.inner.size_hint()
+       }
+
+       #[inline]
+       #[cfg(not(tarpaulin_include))]
+       fn count(self) -> usize {
+               self.inner.count()
+       }
+
+       #[inline]
+       #[cfg(not(tarpaulin_include))]
+       fn last(mut self) -> Option<Self::Item> {
+               self.next_back()
+       }
+}
+
+impl<I> DoubleEndedIterator for Bidi<I>
+where I: DoubleEndedIterator
+{
+       #[inline]
+       fn next_back(&mut self) -> Option<Self::Item> {
+               (&mut self.next_back)(&mut self.inner)
+       }
+
+       #[inline]
+       fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
+               (&mut self.nth_back)(&mut self.inner, n)
+       }
+}
+
+impl<I> ExactSizeIterator for Bidi<I>
+where I: DoubleEndedIterator + ExactSizeIterator
+{
+       #[inline]
+       #[cfg(not(tarpaulin_include))]
+       fn len(&self) -> usize {
+               self.inner.len()
+       }
+}
+
+impl<I> FusedIterator for Bidi<I> where I: DoubleEndedIterator + FusedIterator
+{
+}
+
+/// Extension trait that provides `.bidi()` for all double-ended iterators.
+pub trait BidiIterator
+where
+       Self: Sized + IntoIterator,
+       <Self as IntoIterator>::IntoIter: DoubleEndedIterator,
+{
+       /// Conditionally reverses the direction of iteration.
+       ///
+       /// When `cond` is true, this adapter swaps the `next` and `nth` methods
+       /// with `next_back` and `nth_back`. The resulting iterator is equivalent to
+       /// `if cond { self.rev() } else { self }`.
+       ///
+       /// ## Examples
+       ///
+       /// ```rust
+       /// use wyz::BidiIterator;
+       ///
+       /// let data = [1, 2, 3];
+       /// let mut iter = data.iter().copied().bidi(false);
+       /// assert_eq!(iter.next(), Some(1));
+       /// assert_eq!(iter.next_back(), Some(3));
+       ///
+       /// let mut iter = data.iter().copied().bidi(true);
+       /// assert_eq!(iter.next(), Some(3));
+       /// assert_eq!(iter.next_back(), Some(1));
+       /// ```
+       fn bidi(self, cond: bool) -> Bidi<Self::IntoIter> {
+               Bidi::new(self, cond)
+       }
+}
+
+impl<I> BidiIterator for I
+where
+       I: Sized + IntoIterator,
+       <I as IntoIterator>::IntoIter: DoubleEndedIterator,
+{
+}
+
+#[cfg(test)]
+mod tests {
+       use super::*;
+
+       #[test]
+       fn forward() {
+               let mut iter = (0 .. 6).bidi(false);
+
+               assert_eq!(iter.next(), Some(0));
+               assert_eq!(iter.next_back(), Some(5));
+               assert_eq!(iter.nth(1), Some(2));
+               assert_eq!(iter.nth_back(1), Some(3));
+               assert!(iter.next().is_none());
+       }
+
+       #[test]
+       fn reverse() {
+               let mut iter = (0 .. 6).bidi(true);
+
+               assert_eq!(iter.next(), Some(5));
+               assert_eq!(iter.next_back(), Some(0));
+               assert_eq!(iter.nth(1), Some(3));
+               assert_eq!(iter.nth_back(1), Some(2));
+               assert!(iter.next().is_none());
+       }
+}
diff --git a/src/comu.rs b/src/comu.rs
new file mode 100644 (file)
index 0000000..b6cdcbb
--- /dev/null
@@ -0,0 +1,634 @@
+/*! Trait-level `co`nst/`mu`table tracking.
+
+This module provides a system of marker types that can be used to encode write
+permissions into type parameters rather than duplicate structures.
+!*/
+
+//  This module has no compute logic of its own; it only exists in the
+//  type-system and to forward to the standard library.
+#![cfg(not(tarpaulin_include))]
+
+use core::{
+       cmp,
+       convert::TryFrom,
+       fmt::{
+               self,
+               Debug,
+               Display,
+               Formatter,
+               Pointer,
+       },
+       hash::{
+               Hash,
+               Hasher,
+       },
+       ops::Deref,
+       ptr::NonNull,
+       slice,
+};
+
+use tap::Pipe;
+
+/// A basic `const` marker.
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct Const;
+
+/// A basic `mut` marker.
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct Mut;
+
+/// A frozen wrapper over some other `Mutability` marker.
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct Frozen<Inner>
+where Inner: Mutability
+{
+       inner: Inner,
+}
+
+/** Generalized mutability permissions.
+
+This trait enables referent structures to be generic over the write permissions
+of their referent data. As an example, the standard library defines `*const T`
+and `*mut T` as two duplicate type families, that cannot share any logic at all.
+
+An equivalent library implementation might be `Ptr<T, M: Mutability>`, where
+shared logic can be placed in an `impl<T, M> Ptr<T, M>` block, but unique logic
+(such as freezing a `Mut` pointer, or unfreezing a `Frozen<Mut>`) can be placed
+in specialized `impl<T> Ptr<T, Mut>` blocks.
+**/
+pub trait Mutability: 'static + Copy + Sized + self::seal::Sealed {
+       /// Marks whether this type contains mutability permissions within it.
+       ///
+       /// This is `false` for `Const` and `true` for `Mut`. `Frozen` wrappers
+       /// atop either of these types inherit their interior marker.
+       const CONTAINS_MUTABILITY: bool = false;
+
+       /// Counts the layers of `Frozen<>` wrapping around a base `Const` or `Mut`.
+       const PEANO_NUMBER: usize = 0;
+
+       /// Allow instances to be constructed generically.
+       const SELF: Self;
+
+       /// One of `*const` or `*mut`.
+       const RENDER: &'static str;
+
+       /// Freeze this type, wrapping it in a `const` marker that may later be
+       /// removed to thaw it.
+       fn freeze(self) -> Frozen<Self> {
+               Frozen { inner: self }
+       }
+
+       /// Thaw a previously-frozen type, removing its `Frozen` marker and
+       /// restoring it to `Self`.
+       fn thaw(Frozen { inner }: Frozen<Self>) -> Self {
+               inner
+       }
+}
+
+impl Mutability for Const {
+       const RENDER: &'static str = "*const";
+       const SELF: Self = Self;
+}
+
+impl self::seal::Sealed for Const {
+}
+
+impl<Inner> Mutability for Frozen<Inner>
+where Inner: Mutability + Sized
+{
+       const CONTAINS_MUTABILITY: bool = Inner::CONTAINS_MUTABILITY;
+       const PEANO_NUMBER: usize = 1 + Inner::PEANO_NUMBER;
+       const RENDER: &'static str = Inner::RENDER;
+       const SELF: Self = Self { inner: Inner::SELF };
+}
+
+impl<Inner> self::seal::Sealed for Frozen<Inner> where Inner: Mutability + Sized
+{
+}
+
+impl Mutability for Mut {
+       const CONTAINS_MUTABILITY: bool = true;
+       const RENDER: &'static str = "*mut";
+       const SELF: Self = Self;
+}
+
+impl self::seal::Sealed for Mut {
+}
+
+/** A generic non-null pointer with type-system mutability tracking.
+
+# Type Parameters
+
+- `M`: The mutability permissions of the source pointer.
+- `T`: The referent type of the source pointer.
+**/
+pub struct Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+       /// The address value.
+       inner: NonNull<T>,
+       /// The mutability permissions.
+       comu: M,
+}
+
+impl<M, T> Address<M, T>
+where M: Mutability
+{
+       /// The dangling pointer.
+       pub const DANGLING: Self = Self {
+               inner: NonNull::dangling(),
+               comu: M::SELF,
+       };
+}
+
+impl<M, T> Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+       /// Constructs a new `Address` over some pointer value.
+       ///
+       /// You are responsible for selecting the correct `Mutability` marker.
+       #[inline(always)]
+       pub fn new(addr: NonNull<T>) -> Self {
+               Self {
+                       inner: addr,
+                       comu: M::SELF,
+               }
+       }
+
+       /// Permanently converts an `Address<_>` into an `Address<Const>`.
+       ///
+       /// You should generally prefer [`Address::freeze`].
+       #[inline(always)]
+       pub fn immut(self) -> Address<Const, T> {
+               Address {
+                       inner: self.inner,
+                       comu: Const,
+               }
+       }
+
+       /// Force an `Address<Const>` to be `Address<Mut>`.
+       ///
+       /// ## Safety
+       ///
+       /// You should only call this on addresses you know to have been created
+       /// with `Mut`able permissions and previously removed by [`Address::immut`].
+       ///
+       /// You should prefer using [`Address::freeze`] for temporary, trackable,
+       /// immutability constraints instead.
+       #[inline(always)]
+       pub unsafe fn assert_mut(self) -> Address<Mut, T> {
+               Address {
+                       inner: self.inner,
+                       comu: Mut,
+               }
+       }
+
+       /// Freezes the `Address` so that it is read-only.
+       #[inline(always)]
+       pub fn freeze(self) -> Address<Frozen<M>, T> {
+               let Self { inner, comu } = self;
+               Address {
+                       inner,
+                       comu: comu.freeze(),
+               }
+       }
+
+       /// Removes the `Address` type marker, returning the original pointer.
+       #[inline(always)]
+       pub fn into_inner(self) -> NonNull<T> {
+               self.inner
+       }
+
+       /// Gets the address as a read-only pointer.
+       #[inline(always)]
+       pub fn to_const(self) -> *const T {
+               self.inner.as_ptr() as *const T
+       }
+}
+
+impl<T> Address<Mut, T> {
+       /// Gets the address as a write-capable pointer.
+       #[inline(always)]
+       #[allow(clippy::wrong_self_convention)]
+       pub fn to_mut(self) -> *mut T {
+               self.inner.as_ptr()
+       }
+}
+
+impl<M, T> Address<Frozen<M>, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+       /// Thaws the `Address` to its original mutability permission.
+       #[inline(always)]
+       pub fn thaw(self) -> Address<M, T> {
+               let Self { inner, comu } = self;
+               Address {
+                       inner,
+                       comu: Mutability::thaw(comu),
+               }
+       }
+}
+
+/// Implement `*T -> *T` functions as `Address<T> -> Address<T>`.
+macro_rules! fwd {
+       ($(
+               $(@$unsafe:ident)?
+               $name:ident
+               $(<
+                       $($lt:lifetime),*
+                       $($typaram:ident$(: $($bound:ident),+ $(,)?)?),*
+                       $(,)*
+               >)?
+               $(, $arg:ident: $ty:ty)*
+               $(=> $ret:ty)?
+       );+ $(;)?) => { $(
+               #[doc = concat!("Applies `<*T>::", stringify!($name), "`.")]
+               ///
+               /// See [original documentation][orig].
+               ///
+               #[doc = concat!("[orig]: https://doc.rust-lang.org/std/primitive.pointer.html#method.", stringify!($name))]
+               pub $($unsafe)? fn $name$(<
+                       $($lt,)* $($typaram$(: $($bound),+)?,)*
+               >)?(self$(, $arg: $ty)*) $(-> $ret)? {
+                       self.with_ptr(|ptr| ptr.$name($($arg),*))
+               }
+       )+ };
+}
+
+/// Implement all other pointer functions.
+macro_rules! map {
+       ($(
+               $(@$unsafe:ident)?
+               $name:ident
+               $(<
+                       $($lt:lifetime),*
+                       $($typaram:ident$(: $($bound:ident),+ $(,)?)?),*
+                       $(,)?
+               >)?
+               $(, $arg:ident: $ty:ty $(as $map:expr)?)*
+               $(=> $ret:ty)?
+       );+ $(;)?) => { $(
+               #[doc = concat!("Applies `<*T>::", stringify!($name), "`.")]
+               ///
+               /// See [original documentation][orig].
+               ///
+               #[doc = concat!("[orig]: https://doc.rust-lang.org/std/primitive.pointer.html#method.", stringify!($name))]
+               pub $($unsafe)? fn $name$(<
+                       $($lt,)* $($typaram$(: $($bound),+)?,)*
+               >)?(self$(, $arg: $ty)*) $(-> $ret)? {
+                       self.inner.as_ptr().$name($($arg$(.pipe($map))?),*)
+               }
+       )+ };
+}
+
+/// Port of the pointer inherent methods on `Address`es of `Sized` types.
+#[allow(clippy::missing_safety_doc)]
+impl<M, T> Address<M, T>
+where M: Mutability
+{
+       fwd! {
+               cast<U> => Address<M, U>;
+               @unsafe offset, count: isize => Self;
+               @unsafe add, count: usize => Self;
+               @unsafe sub, count: usize => Self;
+               wrapping_offset, count: isize => Self;
+               wrapping_add, count: usize => Self;
+               wrapping_sub, count: usize => Self;
+       }
+
+       map! {
+               @unsafe offset_from, origin: Self as |orig| orig.to_const() as *mut T => isize;
+               @unsafe read => T;
+               @unsafe read_volatile => T;
+               @unsafe read_unaligned => T;
+               @unsafe copy_to, dest: Address<Mut, T> as Address::to_mut, count: usize;
+               @unsafe copy_to_nonoverlapping, dest: Address<Mut, T> as Address::to_mut, count: usize;
+               align_offset, align: usize => usize;
+       }
+}
+
+/// Port of the pointer inherent methods on `Address`es of any type.
+impl<M, T> Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+       map! {
+               @unsafe as_ref<'a> => Option<&'a T>;
+       }
+
+       /// Applies a pointer -> pointer function within an Address -> Address.
+       #[track_caller]
+       fn with_ptr<U>(self, func: impl FnOnce(*mut T) -> *mut U) -> Address<M, U> {
+               self.inner
+                       .as_ptr()
+                       .pipe(func)
+                       .pipe(NonNull::new)
+                       .unwrap()
+                       .pipe(Address::new)
+       }
+}
+
+/// Port of pointer inherent methods on mutable `Address`es of sized types.
+impl<T> Address<Mut, T> {
+       map! {
+               @unsafe copy_from<M2: Mutability>, src: Address<M2, T> as Address::to_const, count: usize;
+               @unsafe copy_from_nonoverlapping<M2: Mutability>, src: Address<M2, T> as Address::to_const, count: usize;
+               @unsafe write, value: T;
+               @unsafe write_volatile, value: T;
+               @unsafe write_unaligned, value: T;
+               @unsafe replace, src: T => T;
+               @unsafe swap, with: Self as Self::to_mut;
+       }
+}
+
+/// Port of pointer inherent methods on mutable `Address`es of any type.
+impl<T> Address<Mut, T>
+where T: ?Sized
+{
+       map! {
+               @unsafe as_mut<'a> => Option<&'a mut T>;
+               @unsafe drop_in_place;
+       }
+}
+
+impl<M, T> Clone for Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+       #[inline(always)]
+       fn clone(&self) -> Self {
+               *self
+       }
+}
+
+impl<T> TryFrom<*const T> for Address<Const, T>
+where T: ?Sized
+{
+       type Error = NullPtrError;
+
+       #[inline(always)]
+       fn try_from(elem: *const T) -> Result<Self, Self::Error> {
+               NonNull::new(elem as *mut T)
+                       .ok_or(NullPtrError)
+                       .map(Self::new)
+       }
+}
+
+impl<T> From<&T> for Address<Const, T>
+where T: ?Sized
+{
+       #[inline(always)]
+       fn from(elem: &T) -> Self {
+               Self::new(elem.into())
+       }
+}
+
+impl<T> TryFrom<*mut T> for Address<Mut, T>
+where T: ?Sized
+{
+       type Error = NullPtrError;
+
+       #[inline(always)]
+       fn try_from(elem: *mut T) -> Result<Self, Self::Error> {
+               NonNull::new(elem).ok_or(NullPtrError).map(Self::new)
+       }
+}
+
+impl<T> From<&mut T> for Address<Mut, T>
+where T: ?Sized
+{
+       #[inline(always)]
+       fn from(elem: &mut T) -> Self {
+               Self::new(elem.into())
+       }
+}
+
+impl<M, T> Eq for Address<M, T> where M: Mutability
+{
+}
+
+impl<M1, M2, T1, T2> PartialEq<Address<M2, T2>> for Address<M1, T1>
+where
+       M1: Mutability,
+       M2: Mutability,
+{
+       #[inline]
+       fn eq(&self, other: &Address<M2, T2>) -> bool {
+               self.inner.as_ptr() as usize == other.inner.as_ptr() as usize
+       }
+}
+
+impl<M, T> Ord for Address<M, T>
+where M: Mutability
+{
+       #[inline]
+       fn cmp(&self, other: &Self) -> cmp::Ordering {
+               self.partial_cmp(other)
+                       .expect("Addresses have a total ordering")
+       }
+}
+
+impl<M1, M2, T1, T2> PartialOrd<Address<M2, T2>> for Address<M1, T1>
+where
+       M1: Mutability,
+       M2: Mutability,
+{
+       #[inline]
+       fn partial_cmp(&self, other: &Address<M2, T2>) -> Option<cmp::Ordering> {
+               (self.inner.as_ptr() as usize)
+                       .partial_cmp(&(other.inner.as_ptr() as usize))
+       }
+}
+
+impl<M, T> Debug for Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+       #[inline(always)]
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               Debug::fmt(&self.to_const(), fmt)
+       }
+}
+
+impl<M, T> Pointer for Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+       #[inline(always)]
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               Pointer::fmt(&self.to_const(), fmt)
+       }
+}
+
+impl<M, T> Hash for Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+       #[inline(always)]
+       fn hash<H>(&self, state: &mut H)
+       where H: Hasher {
+               self.inner.hash(state)
+       }
+}
+
+impl<M, T> Copy for Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+}
+
+impl<M, T> self::seal::Sealed for Address<M, T>
+where
+       M: Mutability,
+       T: ?Sized,
+{
+}
+
+/// Allows an `Address` to produce an ordinary reference.
+pub trait Referential<'a>: self::seal::Sealed {
+       /// The created reference type. Must be one of `&T` or `&mut T`.
+       type Ref: 'a + Deref;
+
+       /// Converts the `Address` to a reference.
+       ///
+       /// ## Safety
+       ///
+       /// The caller is responsible for ensuring that the memory location that the
+       /// `Address` describes contains an initialized value, and that the produced
+       /// reference abides by the Rust `&`/`&mut` exclusion rules.
+       unsafe fn to_ref(self) -> Self::Ref;
+
+       /// Converts a reference back into an `Address`.
+       fn from_ref(this: Self::Ref) -> Self;
+}
+
+impl<'a, T> Referential<'a> for Address<Const, T>
+where T: 'a + ?Sized
+{
+       type Ref = &'a T;
+
+       unsafe fn to_ref(self) -> Self::Ref {
+               self.inner.as_ref()
+       }
+
+       fn from_ref(this: Self::Ref) -> Self {
+               this.into()
+       }
+}
+
+impl<'a, T> Referential<'a> for Address<Mut, T>
+where T: 'a + ?Sized
+{
+       type Ref = &'a mut T;
+
+       unsafe fn to_ref(mut self) -> Self::Ref {
+               self.inner.as_mut()
+       }
+
+       fn from_ref(this: Self::Ref) -> Self {
+               this.into()
+       }
+}
+
+impl<'a, M, T> Referential<'a> for Address<Frozen<M>, T>
+where
+       M: Mutability,
+       T: 'a + ?Sized,
+{
+       type Ref = &'a T;
+
+       unsafe fn to_ref(self) -> Self::Ref {
+               self.inner.as_ref()
+       }
+
+       fn from_ref(this: Self::Ref) -> Self {
+               Self::new(NonNull::from(this))
+       }
+}
+
+/// A generically-mutable reference.
+pub type Reference<'a, M, T> = <Address<M, T> as Referential<'a>>::Ref;
+
+/// Allows an `Address<M, [T]>` to produce an ordinary slice reference.
+pub trait SliceReferential<'a>: Referential<'a> + self::seal::Sealed {
+       /// The type of the element pointer.
+       type ElementAddr;
+
+       /// Constructs an ordinary slice reference from a base-address and a length.
+       ///
+       /// ## Parameters
+       ///
+       /// - `ptr`: The address of the base element in the slice.
+       /// - `len`: The number of elements, beginning at `ptr`, in the slice.
+       ///
+       /// ## Safety
+       ///
+       /// The base address and the element count must describe a valid region of
+       /// memory.
+       unsafe fn from_raw_parts(ptr: Self::ElementAddr, len: usize) -> Self::Ref;
+}
+
+impl<'a, T> SliceReferential<'a> for Address<Const, [T]>
+where T: 'a
+{
+       type ElementAddr = Address<Const, T>;
+
+       unsafe fn from_raw_parts(ptr: Self::ElementAddr, len: usize) -> Self::Ref {
+               slice::from_raw_parts(ptr.to_const(), len)
+       }
+}
+
+impl<'a, M, T> SliceReferential<'a> for Address<Frozen<M>, [T]>
+where
+       M: Mutability,
+       T: 'a,
+{
+       type ElementAddr = Address<Frozen<M>, T>;
+
+       unsafe fn from_raw_parts(ptr: Self::ElementAddr, len: usize) -> Self::Ref {
+               slice::from_raw_parts(ptr.to_const(), len)
+       }
+}
+
+impl<'a, T> SliceReferential<'a> for Address<Mut, [T]>
+where T: 'a
+{
+       type ElementAddr = Address<Mut, T>;
+
+       unsafe fn from_raw_parts(ptr: Self::ElementAddr, len: usize) -> Self::Ref {
+               slice::from_raw_parts_mut(ptr.to_mut(), len)
+       }
+}
+
+/// [`Address`] cannot be constructed over null pointers.
+#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct NullPtrError;
+
+impl Display for NullPtrError {
+       #[inline]
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               write!(fmt, "wyz::Address cannot contain a null pointer")
+       }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for NullPtrError {
+}
+
+#[doc(hidden)]
+mod seal {
+       #[doc(hidden)]
+       pub trait Sealed {}
+}
diff --git a/src/exit.rs b/src/exit.rs
new file mode 100644 (file)
index 0000000..124a69f
--- /dev/null
@@ -0,0 +1,45 @@
+/*! `exit!` macro
+
+The `exit!` macro simplifies exiting with an error code, and optionally printing
+an error message prior to exit.
+
+# Examples
+
+This example exits with status `1`.
+
+```rust,should_panic
+wyz::exit!();
+```
+
+This example exits with status `2`.
+
+```rust,should_panic
+wyz::exit!(2);
+```
+
+This example exits with status `3`, and uses `eprintln!` to print an error
+message before exiting. Note that if `stderr` has been closed, this will crash
+the program with a panic due to `SIGPIPE`, and *not* call `process::exit()`.
+
+```rust,should_panic
+wyz::exit!(3, "Error status: {}", "testing");
+!*/
+
+#![cfg(feature = "std")]
+
+/// `exit!` macro
+#[macro_export]
+macro_rules! exit {
+       () => {
+               $crate::exit!(1);
+       };
+
+       ( $num:expr $(,)? ) => {
+               ::std::process::exit($num);
+       };
+
+       ( $num:expr, $fmt:expr $( , $arg:expr )* $(,)? ) => {{
+               eprintln!($fmt $( , $arg )*);
+               $crate::exit!($num);
+       }};
+}
diff --git a/src/fmt.rs b/src/fmt.rs
new file mode 100644 (file)
index 0000000..29c2199
--- /dev/null
@@ -0,0 +1,457 @@
+/*! Format forwarding
+
+This module provides wrapper types for each formatting trait other than `Debug`
+which, when `Debug`-formatted, forward to the original trait instead of `Debug`.
+
+Each wrapper type is a tuple struct so that it can be used as a named
+constructor, such as in `.map(FmtDisplay)`. In addition, a blanket trait adds
+extension methods `.fmt_<trait_name>>()` to provide the corresponding wrap.
+
+Any modifiers in the format template string or struct modifier are passed
+through to the desired trait implementation unchanged. The only effect of the
+forwarding types in this module is to change the `?` template character to one
+of the other trait signifiers.
+!*/
+
+use core::{
+       fmt::{
+               self,
+               Binary,
+               Debug,
+               Display,
+               Formatter,
+               LowerExp,
+               LowerHex,
+               Octal,
+               Pointer,
+               UpperExp,
+               UpperHex,
+       },
+       ops::{
+               Deref,
+               DerefMut,
+       },
+};
+
+/// Wraps any value with a format-forward to `Debug`.
+#[cfg(not(tarpaulin_include))]
+pub trait FmtForward: Sized {
+       /// Causes `self` to use its `Binary` implementation when `Debug`-formatted.
+       #[inline(always)]
+       fn fmt_binary(self) -> FmtBinary<Self>
+       where Self: Binary {
+               FmtBinary(self)
+       }
+
+       /// Causes `self` to use its `Display` implementation when
+       /// `Debug`-formatted.
+       #[inline(always)]
+       fn fmt_display(self) -> FmtDisplay<Self>
+       where Self: Display {
+               FmtDisplay(self)
+       }
+
+       /// Causes `self` to use its `LowerExp` implementation when
+       /// `Debug`-formatted.
+       #[inline(always)]
+       fn fmt_lower_exp(self) -> FmtLowerExp<Self>
+       where Self: LowerExp {
+               FmtLowerExp(self)
+       }
+
+       /// Causes `self` to use its `LowerHex` implementation when
+       /// `Debug`-formatted.
+       #[inline(always)]
+       fn fmt_lower_hex(self) -> FmtLowerHex<Self>
+       where Self: LowerHex {
+               FmtLowerHex(self)
+       }
+
+       /// Causes `self` to use its `Octal` implementation when `Debug`-formatted.
+       #[inline(always)]
+       fn fmt_octal(self) -> FmtOctal<Self>
+       where Self: Octal {
+               FmtOctal(self)
+       }
+
+       /// Causes `self` to use its `Pointer` implementation when
+       /// `Debug`-formatted.
+       #[inline(always)]
+       fn fmt_pointer(self) -> FmtPointer<Self>
+       where Self: Pointer {
+               FmtPointer(self)
+       }
+
+       /// Causes `self` to use its `UpperExp` implementation when
+       /// `Debug`-formatted.
+       #[inline(always)]
+       fn fmt_upper_exp(self) -> FmtUpperExp<Self>
+       where Self: UpperExp {
+               FmtUpperExp(self)
+       }
+
+       /// Causes `self` to use its `UpperHex` implementation when
+       /// `Debug`-formatted.
+       #[inline(always)]
+       fn fmt_upper_hex(self) -> FmtUpperHex<Self>
+       where Self: UpperHex {
+               FmtUpperHex(self)
+       }
+
+       /// Formats each item in a sequence.
+       ///
+       /// This wrapper structure conditionally implements all of the formatting
+       /// traits when `self` can be viewed as an iterator whose *items* implement
+       /// them. It iterates over `&self` and prints each item according to the
+       /// formatting specifier provided.
+       #[inline(always)]
+       fn fmt_list(self) -> FmtList<Self>
+       where for<'a> &'a Self: IntoIterator {
+               FmtList(self)
+       }
+}
+
+impl<T: Sized> FmtForward for T {
+}
+
+/// Forwards a type’s `Binary` formatting implementation to `Debug`.
+#[repr(transparent)]
+pub struct FmtBinary<T: Binary>(pub T);
+
+/// Forwards a type’s `Display` formatting implementation to `Debug`.
+#[repr(transparent)]
+pub struct FmtDisplay<T: Display>(pub T);
+
+/// Renders each element of a stream into a list.
+#[repr(transparent)]
+pub struct FmtList<T>(pub T)
+where for<'a> &'a T: IntoIterator;
+
+/// Forwards a type’s `LowerExp` formatting implementation to `Debug`.
+#[repr(transparent)]
+pub struct FmtLowerExp<T: LowerExp>(pub T);
+
+/// Forwards a type’s `LowerHex` formatting implementation to `Debug`.
+#[repr(transparent)]
+pub struct FmtLowerHex<T: LowerHex>(pub T);
+
+/// Forwards a type’s `Octal` formatting implementation to `Debug`.
+#[repr(transparent)]
+pub struct FmtOctal<T: Octal>(pub T);
+
+/// Forwards a type’s `Pointer` formatting implementation to `Debug`.
+#[repr(transparent)]
+pub struct FmtPointer<T: Pointer>(pub T);
+
+/// Forwards a type’s `UpperExp` formatting implementation to `Debug`.
+#[repr(transparent)]
+pub struct FmtUpperExp<T: UpperExp>(pub T);
+
+/// Forwards a type’s `UpperHex` formatting implementation to `Debug`.
+#[repr(transparent)]
+pub struct FmtUpperHex<T: UpperHex>(pub T);
+
+macro_rules! fmt {
+       ($($w:ty => $t:ident),* $(,)?) => { $(
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t + Binary> Binary for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               Binary::fmt(&self.0, fmt)
+                       }
+               }
+
+               impl<T: $t> Debug for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               <T as $t>::fmt(&self.0, fmt)
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t + Display> Display for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               Display::fmt(&self.0, fmt)
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t + LowerExp> LowerExp for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               LowerExp::fmt(&self.0, fmt)
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t + LowerHex> LowerHex for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               LowerHex::fmt(&self.0, fmt)
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t + Octal> Octal for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               Octal::fmt(&self.0, fmt)
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t + Pointer> Pointer for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               Pointer::fmt(&self.0, fmt)
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t + UpperExp> UpperExp for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               UpperExp::fmt(&self.0, fmt)
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t + UpperHex> UpperHex for $w {
+                       #[inline(always)]
+                       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+                               UpperHex::fmt(&self.0, fmt)
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t> Deref for $w {
+                       type Target = T;
+
+                       #[inline(always)]
+                       fn deref(&self) -> &Self::Target {
+                               &self.0
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t> DerefMut for $w {
+                       #[inline(always)]
+                       fn deref_mut(&mut self) -> &mut Self::Target {
+                               &mut self.0
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t> AsRef<T> for $w {
+                       #[inline(always)]
+                       fn as_ref(&self) -> &T {
+                               &self.0
+                       }
+               }
+
+               #[cfg(not(tarpaulin_include))]
+               impl<T: $t> AsMut<T> for $w {
+                       #[inline(always)]
+                       fn as_mut(&mut self) -> &mut T {
+                               &mut self.0
+                       }
+               }
+       )* };
+}
+
+fmt!(
+       FmtBinary<T> => Binary,
+       FmtDisplay<T> => Display,
+       FmtLowerExp<T> => LowerExp,
+       FmtLowerHex<T> => LowerHex,
+       FmtOctal<T> => Octal,
+       FmtPointer<T> => Pointer,
+       FmtUpperExp<T> => UpperExp,
+       FmtUpperHex<T> => UpperHex,
+);
+
+impl<T> Binary for FmtList<T>
+where
+       for<'a> &'a T: IntoIterator,
+       for<'a> <&'a T as IntoIterator>::Item: Binary,
+{
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               fmt.debug_list()
+                       .entries((&self.0).into_iter().map(FmtBinary))
+                       .finish()
+       }
+}
+
+impl<T> Debug for FmtList<T>
+where
+       for<'a> &'a T: IntoIterator,
+       for<'a> <&'a T as IntoIterator>::Item: Debug,
+{
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               fmt.debug_list().entries((&self.0).into_iter()).finish()
+       }
+}
+
+impl<T> Display for FmtList<T>
+where
+       for<'a> &'a T: IntoIterator,
+       for<'a> <&'a T as IntoIterator>::Item: Display,
+{
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               fmt.debug_list()
+                       .entries((&self.0).into_iter().map(FmtDisplay))
+                       .finish()
+       }
+}
+
+impl<T> LowerExp for FmtList<T>
+where
+       for<'a> &'a T: IntoIterator,
+       for<'a> <&'a T as IntoIterator>::Item: LowerExp,
+{
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               fmt.debug_list()
+                       .entries((&self.0).into_iter().map(FmtLowerExp))
+                       .finish()
+       }
+}
+
+impl<T> LowerHex for FmtList<T>
+where
+       for<'a> &'a T: IntoIterator,
+       for<'a> <&'a T as IntoIterator>::Item: LowerHex,
+{
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               fmt.debug_list()
+                       .entries((&self.0).into_iter().map(FmtLowerHex))
+                       .finish()
+       }
+}
+
+impl<T> Octal for FmtList<T>
+where
+       for<'a> &'a T: IntoIterator,
+       for<'a> <&'a T as IntoIterator>::Item: Octal,
+{
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               fmt.debug_list()
+                       .entries((&self.0).into_iter().map(FmtOctal))
+                       .finish()
+       }
+}
+
+impl<T> UpperExp for FmtList<T>
+where
+       for<'a> &'a T: IntoIterator,
+       for<'a> <&'a T as IntoIterator>::Item: UpperExp,
+{
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               fmt.debug_list()
+                       .entries((&self.0).into_iter().map(FmtUpperExp))
+                       .finish()
+       }
+}
+
+impl<T> UpperHex for FmtList<T>
+where
+       for<'a> &'a T: IntoIterator,
+       for<'a> <&'a T as IntoIterator>::Item: UpperHex,
+{
+       fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+               fmt.debug_list()
+                       .entries((&self.0).into_iter().map(FmtUpperHex))
+                       .finish()
+       }
+}
+
+#[cfg(not(tarpaulin_include))]
+impl<T> Deref for FmtList<T>
+where for<'a> &'a T: IntoIterator
+{
+       type Target = T;
+
+       #[inline(always)]
+       fn deref(&self) -> &Self::Target {
+               &self.0
+       }
+}
+
+#[cfg(not(tarpaulin_include))]
+impl<T> DerefMut for FmtList<T>
+where for<'a> &'a T: IntoIterator
+{
+       #[inline(always)]
+       fn deref_mut(&mut self) -> &mut Self::Target {
+               &mut self.0
+       }
+}
+
+#[cfg(not(tarpaulin_include))]
+impl<T> AsRef<T> for FmtList<T>
+where for<'a> &'a T: IntoIterator
+{
+       #[inline(always)]
+       fn as_ref(&self) -> &T {
+               &self.0
+       }
+}
+
+#[cfg(not(tarpaulin_include))]
+impl<T> AsMut<T> for FmtList<T>
+where for<'a> &'a T: IntoIterator
+{
+       #[inline(always)]
+       fn as_mut(&mut self) -> &mut T {
+               &mut self.0
+       }
+}
+
+#[cfg(all(test, feature = "alloc"))]
+mod tests {
+       #[cfg(not(feature = "std"))]
+       use alloc::format;
+
+       #[cfg(feature = "std")]
+       use std::format;
+
+       use super::*;
+
+       #[test]
+       fn render_item() {
+               let num = 29;
+
+               assert_eq!(format!("{:?}", num.fmt_binary()), "11101");
+               assert_eq!(format!("{:?}", num.fmt_display()), "29");
+               assert_eq!(format!("{:?}", num.fmt_upper_hex()), "1D");
+               assert_eq!(format!("{:?}", num.fmt_octal()), "35");
+               assert_eq!(format!("{:?}", num.fmt_lower_hex()), "1d");
+
+               let num = 53.7;
+               assert_eq!(format!("{:?}", num.fmt_lower_exp()), "5.37e1");
+               assert_eq!(format!("{:?}", num.fmt_upper_exp()), "5.37E1");
+       }
+
+       #[test]
+       fn render_list() {
+               let list = [0, 1, 2, 3];
+               assert_eq!(format!("{:02b}", list.fmt_list()), "[00, 01, 10, 11]");
+               assert_eq!(format!("{:01?}", list.fmt_list()), "[0, 1, 2, 3]");
+               assert_eq!(format!("{:01}", list.fmt_list()), "[0, 1, 2, 3]");
+
+               let list = [-51.0, -1.2, 1.3, 54.0];
+               assert_eq!(
+                       format!("{:e}", list.fmt_list()),
+                       "[-5.1e1, -1.2e0, 1.3e0, 5.4e1]"
+               );
+               assert_eq!(
+                       format!("{:E}", list.fmt_list()),
+                       "[-5.1E1, -1.2E0, 1.3E0, 5.4E1]"
+               );
+
+               let list = [0, 10, 20, 30];
+               assert_eq!(format!("{:02x}", list.fmt_list()), "[00, 0a, 14, 1e]");
+               assert_eq!(format!("{:02o}", list.fmt_list()), "[00, 12, 24, 36]");
+               assert_eq!(format!("{:02X}", list.fmt_list()), "[00, 0A, 14, 1E]");
+       }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..ef81269
--- /dev/null
@@ -0,0 +1,46 @@
+/*! `wyz` – myrrlyn’s wyzyrdly library
+
+This crate consolidates all the small tools and conveniences I’ve built up in my
+experience building Rust crates.
+
+Each module has more documentation about what it contains. The modules are
+largely independent, and can be used individually.
+!*/
+
+#![no_std]
+#![cfg_attr(debug_assertions, warn(missing_docs))]
+#![cfg_attr(not(debug_assertions), deny(missing_docs))]
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+#[cfg(feature = "std")]
+extern crate std;
+
+pub mod bidi;
+pub mod comu;
+pub mod fmt;
+pub mod range;
+
+#[cfg(all(feature = "std", feature = "garbage"))]
+pub mod wm;
+
+#[cfg(feature = "std")]
+#[macro_use]
+pub mod exit;
+
+pub use self::{
+       bidi::*,
+       comu::*,
+       fmt::*,
+       range::*,
+};
+
+#[cfg(feature = "std")]
+pub use self::exit::*;
+
+#[cfg(all(feature = "std", feature = "garbage"))]
+pub use self::wm::{
+       BgDrop,
+       BgDropExt,
+};
diff --git a/src/range.rs b/src/range.rs
new file mode 100644 (file)
index 0000000..2453762
--- /dev/null
@@ -0,0 +1,179 @@
+//! Range utilities.
+
+use core::ops::{
+       Bound,
+       Range,
+       RangeBounds,
+};
+
+/// Extension methods for working with various range types.
+pub trait RangeExt<T>: RangeBounds<T>
+where T: Ord
+{
+       /// Normalizes a range-like type to a canonical half-open `Range`.
+       ///
+       /// ## Parameters
+       ///
+       /// - `self`: The range to normalize.
+       /// - `start`: An optional fallback *inclusive* lower bound.
+       /// - `end`: An optional fallback *exclusive* upper bound.
+       ///
+       /// ## Returns
+       ///
+       /// A `Range` whose start and end values are the following, in order of
+       /// decreasing priority:
+       ///
+       /// - `self.start()`, or if absent, the `start` parameter, or if it is
+       ///   `None`, `0`.
+       /// - `self.end()`, or if absent, the `end` parameter, or if it is `None`,
+       ///   !0`.
+       fn normalize(
+               self,
+               start: impl Into<Option<T>>,
+               end: impl Into<Option<T>>,
+       ) -> Range<T>;
+
+       /// Finds the intersection between two range-likes. The produced `Range`
+       /// spans only the elements common to both.
+       ///
+       /// This returns `None` if the ranges do not have an intersection (at least
+       /// one element present in both ranges).
+       fn intersection<R>(self, other: R) -> Option<Range<T>>
+       where R: RangeExt<T>;
+
+       /// Finds the union between two range-likes. The produced `Range` spans all
+       /// elements present in at least one of them.
+       ///
+       /// This returns `None` if the ranges do not have an intersection (at least
+       /// one element present in both ranges).
+       fn union<R>(self, other: R) -> Option<Range<T>>
+       where R: RangeExt<T>;
+}
+
+//  TODO(myrrlyn): Use funty to extend this for all integers.
+impl<R> RangeExt<usize> for R
+where R: RangeBounds<usize>
+{
+       fn normalize(
+               self,
+               start: impl Into<Option<usize>>,
+               end: impl Into<Option<usize>>,
+       ) -> Range<usize> {
+               let start = match self.start_bound() {
+                       Bound::Unbounded => start.into().unwrap_or(0),
+                       Bound::Included(&v) => v,
+                       Bound::Excluded(&v) => v.saturating_add(1),
+               };
+               let end = match self.end_bound() {
+                       Bound::Unbounded => end.into().unwrap_or(!0),
+                       Bound::Included(&v) => v.saturating_add(1),
+                       Bound::Excluded(&v) => v,
+               };
+               if start > end {
+                       end .. start
+               }
+               else {
+                       start .. end
+               }
+       }
+
+       fn intersection<R2>(self, other: R2) -> Option<Range<usize>>
+       where R2: RangeExt<usize> {
+               let Range { start: a1, end: a2 } = self.normalize(None, None);
+               let Range { start: b1, end: b2 } = other.normalize(None, None);
+               if b1 < a1 {
+                       return (b1 .. b2).intersection(a1 .. a2);
+               }
+               if !(a1 .. a2).contains(&b1) {
+                       return None;
+               }
+               let start = a1.max(b1);
+               let end = a2.min(b2);
+               if start > end {
+                       Some(end .. start)
+               }
+               else {
+                       Some(start .. end)
+               }
+       }
+
+       fn union<R2>(self, other: R2) -> Option<Range<usize>>
+       where R2: RangeExt<usize> {
+               let Range { start: a1, end: a2 } = self.normalize(None, None);
+               let Range { start: b1, end: b2 } = other.normalize(None, None);
+               if b1 < a1 {
+                       return (b1 .. b2).intersection(a1 .. a2);
+               }
+               if !(a1 .. a2).contains(&b1) {
+                       return None;
+               }
+               let start = a1.min(b1);
+               let end = a2.max(b2);
+               if start > end {
+                       Some(end .. start)
+               }
+               else {
+                       Some(start .. end)
+               }
+       }
+}
+
+#[cfg(test)]
+mod tests {
+       use super::*;
+
+       #[test]
+       fn normalize() {
+               let r = (..).normalize(1, 10);
+               assert!(r.contains(&1));
+               assert!(r.contains(&9));
+               assert!(!r.contains(&0));
+               assert!(!r.contains(&10));
+
+               let r = (.. 10).normalize(1, 20);
+               assert!(r.contains(&1));
+               assert!(r.contains(&9));
+               assert!(!r.contains(&0));
+               assert!(!r.contains(&10));
+
+               let r = (4 ..).normalize(6, 10);
+               assert!(r.contains(&4));
+               assert!(r.contains(&9));
+               assert!(!r.contains(&3));
+               assert!(!r.contains(&10));
+
+               let r = (4 ..= 10).normalize(6, 8);
+               assert!(r.contains(&4));
+               assert!(r.contains(&10));
+               assert!(!r.contains(&3));
+               assert!(!r.contains(&11));
+
+               let r = (..= 10).normalize(1, 8);
+               assert!(r.contains(&1));
+               assert!(r.contains(&10));
+               assert!(!r.contains(&0));
+               assert!(!r.contains(&11));
+       }
+
+       #[test]
+       fn intersect() {
+               let a = 3 .. 10;
+               let b = 7 ..= 20;
+               assert_eq!(a.intersection(b), Some(7 .. 10));
+
+               let c = 3 .. 10;
+               let d = 13 ..= 20;
+               assert!(c.intersection(d).is_none());
+       }
+
+       #[test]
+       fn union() {
+               let a = 3 .. 10;
+               let b = 7 ..= 20;
+               assert_eq!(a.union(b), Some(3 .. 21));
+
+               let c = 3 .. 10;
+               let d = 13 ..= 20;
+               assert!(c.union(d).is_none());
+       }
+}
diff --git a/src/wm.rs b/src/wm.rs
new file mode 100644 (file)
index 0000000..624df6c
--- /dev/null
+++ b/src/wm.rs
@@ -0,0 +1,466 @@
+/*! Waste Management
+
+Do you drive your own garbage to the landfill or compost heap? Maybe you should,
+but that’s a lot of work and takes time out of your day, so you probably don’t.
+Instead, you give it to a worker that specializes in managing objects at the end
+of their time in your service.
+
+This module moves objects from the threads where they were working to a single,
+global, worker thread when they go out of scope. Since an object that is going
+out of scope can no longer be used, you could say that it is *garbage*; since
+there is only one worker thread to receive all such objects, you could say that
+the worker *collects* them. Wink wink, nudge nudge.
+
+Users need only wrap their values in `BgDrop` to have their garbage collected.
+`BgDrop` only accepts `'static` values, since the values are being sent to
+another thread that makes no guarantees about timeliness of destruction, and
+thus the garbage cannot have any lingering ties to live objects in the rest of
+the program.
+
+When a `BgDrop` goes out of scope, it attempts to send its interior value to the
+collector thread. The first `BgDrop` to drop must start the collector thread,
+which may result in an indefinite block until the thread begins. Once the
+collector is running, all `BgDrop` drops will *attempt* to send their internal
+value to the collector for destruction. If the send fails, then the value will
+be dropped on the sending thread, rather than on the collector.
+
+You can prevent future collections with `cancel_collection()`, which destroys
+the channel used to move values to the collector thread. You can also get the
+thread key for the collector with `collector()`. If you need to ensure that all
+pending destructions occur before program exit, you should end your program with
+a `cancel_collection()` and then `collector().unwrap().join()`. The collector
+guarantees that objects queued for destruction are either enqueued for future
+destruction *or* destroyed immediately, so the collector thread *will* receive a
+signal for each object not destroyed on its prior thread.
+!*/
+
+#![cfg(all(feature = "std", feature = "garbage"))]
+
+use once_cell::sync::OnceCell;
+
+use tap::Pipe;
+
+use std::{
+       collections::VecDeque,
+       marker::PhantomData,
+       mem::{
+               self,
+               ManuallyDrop,
+       },
+       ops::{
+               Deref,
+               DerefMut,
+       },
+       sync::{
+               mpsc,
+               Mutex,
+               MutexGuard,
+               Once,
+               RwLock,
+       },
+       thread,
+};
+
+use typemap::TypeMap;
+
+/** Run an object’s destructor in the background.
+
+When `BgDrop`-wrapped objects go out of scope, the `BgDrop` destructor attempts
+to use a global background-thread to receive the wrapped value, so that its
+destructor is run on the worker thread. If the thread running a `BgDrop`
+destructor is able to send the value to the worker, then it resumes immediately,
+and does not wait for the worker to get around to actually running the wrapped
+destructor. This is similar to the disposal semantics of many GC systems, though
+the actual system used to determine when an object becomes garbage is still the
+compiler’s static lifetime analyzer.
+
+All `BgDrop` types use the same persistent worker thread, minimizing the program
+cost of deferral.
+
+If the function [`wm::shutdown()`] is called, all future `BgDrop`s become a noöp
+and run their contained destructors on their local threads.
+
+[`wm::shutdown()`]: ../fn.shutdown.html
+**/
+#[repr(transparent)]
+pub struct BgDrop<T: 'static> {
+       inner: ManuallyDrop<T>,
+}
+
+impl<T: 'static> BgDrop<T> {
+       /// Instructs an object to run its destructor in the background.
+       ///
+       /// This function modifies the wrapped object’s `Drop` implementation to try
+       /// to, on `drop`, send the inner object to a background thread for actual
+       /// destruction. If the object cannot be sent to the background when its
+       /// wrapper goes out of scope, then its destructor runs immediately, in the
+       /// thread that had been holding the object when the modified destructor was
+       /// called.
+       ///
+       /// If the wrapped object is successfully sent to the background, the
+       /// modified destructor exits, and the current thread resumes work. Once
+       /// enqueued, the inner object is guaranteed to be *eventually* destroyed,
+       /// unless the program exits in a manner that prevents the background
+       /// collector from emptying its work queue.
+       #[inline(always)]
+       pub fn new(value: T) -> Self {
+               Self {
+                       inner: ManuallyDrop::new(value),
+               }
+       }
+
+       /// Removes the background-destruction marker, returning the interior value.
+       #[inline(always)]
+       pub fn into_inner(mut self) -> T {
+               unsafe { ManuallyDrop::take(&mut self.inner) }
+       }
+
+       /// Attempt to prevent double-deferral, which would cause the outer to send
+       /// the inner to the worker thread, making the worker thread send the
+       /// *actual* inner to itself for destruction. This is safe, but stupid.
+       #[inline(always)]
+       #[doc(hidden)]
+       pub fn bg_drop(self) -> Self {
+               self
+       }
+
+       #[inline(always)]
+       fn dtor(&mut self) {
+               //  No destructor, no problem! Quit.
+               if !mem::needs_drop::<T>() {
+                       return;
+               }
+
+               //  Ensure that the collector has been initialized.
+               init();
+
+               //  Pull the value into local scope, reärming the destructor.
+               let val = unsafe { ManuallyDrop::take(&mut self.inner) };
+
+               //  Get a local copy of the outbound channel, or exit.
+               let sender = match sender() {
+                       Some(s) => s,
+                       None => return,
+               };
+
+               //  Enqueue the object into the transfer buffer.
+               dq().entry::<Key<T>>()
+                       .or_insert_with(VecDeque::new)
+                       .pipe(|v| v.push_back(val));
+
+               //  Send the dequeueïng destructor to the collector thread, or run it
+               //  locally if the send failed.
+               if sender.send(dtor::<T>).is_err() {
+                       dtor::<T>();
+               }
+       }
+}
+
+impl<T: 'static> AsRef<T> for BgDrop<T> {
+       fn as_ref(&self) -> &T {
+               &*self.inner
+       }
+}
+
+impl<T: 'static> AsMut<T> for BgDrop<T> {
+       fn as_mut(&mut self) -> &mut T {
+               &mut *self.inner
+       }
+}
+
+impl<T: 'static> Deref for BgDrop<T> {
+       type Target = T;
+
+       fn deref(&self) -> &Self::Target {
+               &*self.inner
+       }
+}
+
+impl<T: 'static> DerefMut for BgDrop<T> {
+       fn deref_mut(&mut self) -> &mut Self::Target {
+               &mut *self.inner
+       }
+}
+
+impl<T: 'static> Drop for BgDrop<T> {
+       fn drop(&mut self) {
+               self.dtor();
+       }
+}
+
+/// Attaches a `BgDrop` constructor to all suitable types.
+pub trait BgDropExt: Sized + 'static {
+       /// Modifies the object’s destructor to run in the background.
+       ///
+       /// When this value goes out of scope, it will attempt to send itself to a
+       /// background thread where its *actual* destructor will be run. The actual
+       /// destructor will run on the local thread only if the transfer to the
+       /// background worker was unable to occur.
+       ///
+       /// The background worker is started only when the first value marked for
+       /// deferred destruction actually drops, so the first call will block until
+       /// the disposal system is initialized.
+       ///
+       /// The first value of each *type* to be deferred will modify the disposal
+       /// system to handle its type.
+       ///
+       /// All subsequent drops of a type that has been deferred before will happen
+       /// nearly instantaneously, as they must only observe that the system is set
+       /// up for them, and move the value into the transfer queue.
+       ///
+       /// # Usage
+       ///
+       /// ```rust
+       /// use wyz::wm::BgDropExt;
+       ///
+       /// vec![1, 2, 3, 4, 5].bg_drop();
+       /// vec![6, 7, 8, 9, 10].bg_drop();
+       /// ```
+       ///
+       /// If you need to guarantee that your program remains open until all
+       /// deferred objects are destroyed, you can block on [`wm::shutdown()`].
+       ///
+       /// [`wm::shutdown()`]: ../fn.shutdown.html
+       fn bg_drop(self) -> BgDrop<Self> {
+               BgDrop::new(self)
+       }
+}
+
+impl<T: Sized + 'static> BgDropExt for T {
+}
+
+/** Stop the background disposal system.
+
+This function shuts down the disposal system, and ensures that all deferred
+destructors in the program are correctly handled. It disables all *future*
+deferred-drops from sending values to the worker thread, which forces them to
+run their destructors locally. In the meantime, the worker thread will pull all
+remaining values out of its work queue and destroy them, then terminate once it
+sees that its queue has been closed.
+
+When this function returns, the worker thread will have emptied its queue, torn
+down its transfer system, and exited.
+
+You may call this function more than once; it is idempotent. The worker system
+is program-global, and will only be started once and stopped once. Once this
+function is called, the program will never run deferred disposal again.
+
+Rust does not provide a portable `atexit` behavior, so you are responsible for
+calling this before your program terminates if you want to ensure that all
+deferred destructions actually take place. Future versions of this library may
+register `wm::shutdown()` with the sytem `atexit` handler. If this occurs, the
+function will be marked as deprecated on platforms where it is set.
+**/
+pub fn shutdown() {
+       static STOP: Once = Once::new();
+       STOP.call_once(|| {
+               //  Destroy the sender handle.
+               let _: Option<AssertThreadsafe<mpsc::Sender<Dtor>>> = SEND
+                       .get()
+                       //  Lock the write guard,
+                       .and_then(|rw| rw.write().ok())
+                       //  And remove the sender handle from it.
+                       .and_then(|mut sender| sender.take());
+               //  Close the destructor thread.
+               let _: Option<()> = JOIN
+                       .get()
+                       //  Lock the thread’s mutex,
+                       .and_then(|mx| mx.lock().ok())
+                       //  Remove the handle from it,
+                       .and_then(|mut mg| mg.take())
+                       //  And await the thread’s termination.
+                       .and_then(|jh| jh.join().ok());
+       });
+}
+
+//  Disposal system implementation
+
+type Dtor = fn() -> ();
+
+//  The sender is never used concurrently.
+static SEND: OnceCell<RwLock<Option<AssertThreadsafe<mpsc::Sender<Dtor>>>>> =
+       OnceCell::new();
+//  The map is only ever used behind a mutex lock.
+static DUMP: OnceCell<Mutex<AssertThreadsafe<TypeMap>>> = OnceCell::new();
+static JOIN: OnceCell<Mutex<Option<thread::JoinHandle<()>>>> = OnceCell::new();
+
+/// Initialize the collection system.
+#[inline(never)]
+fn init() {
+       let (send, recv) = mpsc::channel::<Dtor>();
+
+       //  Establish a base sending channel. This holds the collector open until
+       //  `cancel()` is called.
+       SEND.get_or_init(|| {
+               send.pipe(AssertThreadsafe::new)
+                       .pipe(Some)
+                       .pipe(RwLock::new)
+       });
+
+       //  Establish a transfer queue for all types.
+       DUMP.get_or_init(|| {
+               TypeMap::new().pipe(AssertThreadsafe::new).pipe(Mutex::new)
+       });
+
+       //  Start the collector thread.
+       JOIN.get_or_init(|| {
+               thread::spawn(move || {
+                       while let Ok(ev) = recv.recv() {
+                               (ev)()
+                       }
+                       let _ = mem::replace(&mut **dq(), TypeMap::new());
+               })
+               .pipe(Some)
+               .pipe(Mutex::new)
+       });
+
+       //  TODO(myrrlyn): Register an `atexit` handler to run `shutdown()`.
+}
+
+/// Lock the transfer map.
+fn dq() -> MutexGuard<'static, AssertThreadsafe<TypeMap>> {
+       unsafe { DUMP.get_unchecked() }
+               .lock()
+               .expect("Collection buffer should never observe a panic")
+}
+
+/// Pull the front object out of a typed queue, and destroy it.
+fn dtor<T: 'static>() {
+       //  Binding a value causes it to drop *after* any temporaries created in its
+       //  construction.
+       let _tmp = dq()
+               //  View the deque containing objects of this type.
+               .get_mut::<Key<T>>()
+               //  And pop the front value in the queue. It is acceptable to fail.
+               .and_then(VecDeque::pop_front);
+       //  The mutex lock returned by `dq()` drops immediately after the semicolon,
+       //  and the `_tmp` binding drops immediately before the terminating brace.
+}
+
+/// Get a local copy of the sender, free of threading concerns.
+fn sender() -> Option<mpsc::Sender<Dtor>> {
+       //  `sender` is only called after `SEND` is initialized
+       unsafe { SEND.get_unchecked() }
+               //  Quit if the send channel could not be opened for reading
+               .read()
+               .ok()?
+               //  or if it contains `None`
+               .as_ref()?
+               .inner
+               //  and copy the sender into the local thread.
+               .clone()
+               .pipe(Some)
+}
+
+/// Look up a type’s location in the transfer map.
+struct Key<T: 'static>(PhantomData<T>);
+
+impl<T: 'static> typemap::Key for Key<T> {
+       /// The transfer map holds some form of collection of the transferred types.
+       ///
+       /// The specific collection type is irrelevant, as long as it supports both
+       /// insertion and removal, and has reasonable behavior characteristics.
+       /// Since the map has to be completely locked for any transfer event, as the
+       /// first transfer of each type must insert its queue into the map, there is
+       /// no advantage in trying to make this mpsc-friendly.
+       ///
+       /// If Rust were to allow a construction like
+       ///
+       /// ```rust,ignore
+       /// fn type_chan<T: 'static>() -> (
+       ///   &'static mpsc::Sender<T>,
+       ///   &'static mpsc::Receiver<T>,
+       /// ) {
+       ///   static MAKE: Once = Once::new();
+       ///   static mut CHAN: Option<(mpsc::Sender<T>, mpsc::Receiver<T>) = None;
+       ///   MAKE.call_once(|| unsafe {
+       ///     CHAN = Some(mpsc::channel());
+       ///   });
+       ///   (&CHAN.0, &CHAN.1)
+       /// }
+       /// ```
+       ///
+       /// then a dynamic type-map would not be necessary at all, since each type
+       /// would be granted its own dedicated channel at compile-time. But Rust
+       /// does not, so, it is.
+       type Value = VecDeque<T>;
+}
+
+/** Get off my back, `rustc`.
+
+This is required because `static` vars must be `Sync`, and the thread-safety
+wrappers in use apparently inherit the `Sync`hronicity of their wrapped types.
+This module uses them correctly, and does not permit escape, so this struct is
+needed to get the compiler to accept our use.
+**/
+#[repr(transparent)]
+struct AssertThreadsafe<T> {
+       inner: T,
+}
+
+impl<T> AssertThreadsafe<T> {
+       fn new(inner: T) -> Self {
+               Self { inner }
+       }
+}
+
+unsafe impl<T> Send for AssertThreadsafe<T> {
+}
+
+unsafe impl<T> Sync for AssertThreadsafe<T> {
+}
+
+impl<T> Deref for AssertThreadsafe<T> {
+       type Target = T;
+
+       fn deref(&self) -> &Self::Target {
+               &self.inner
+       }
+}
+
+impl<T> DerefMut for AssertThreadsafe<T> {
+       fn deref_mut(&mut self) -> &mut Self::Target {
+               &mut self.inner
+       }
+}
+
+#[cfg(test)]
+mod tests {
+       use super::*;
+       use std::{
+               sync::atomic::{
+                       AtomicUsize,
+                       Ordering::Relaxed,
+               },
+               thread,
+               time::Duration,
+       };
+
+       #[test]
+       fn trash_pickup() {
+               static COUNTER: AtomicUsize = AtomicUsize::new(0);
+
+               struct Deferrer<F: FnMut()>(F);
+
+               impl<F: FnMut()> Drop for Deferrer<F> {
+                       fn drop(&mut self) {
+                               (self.0)()
+                       }
+               }
+
+               let kept = Deferrer(|| {
+                       COUNTER.fetch_add(1, Relaxed);
+               });
+               let sent = Deferrer(|| {
+                       COUNTER.fetch_add(1, Relaxed);
+               })
+               .bg_drop();
+
+               drop(kept);
+               drop(sent);
+
+               while COUNTER.load(Relaxed) < 2 {
+                       thread::sleep(Duration::from_millis(100));
+               }
+       }
+}
diff --git a/tarpaulin.toml b/tarpaulin.toml
new file mode 100644 (file)
index 0000000..51e040e
--- /dev/null
@@ -0,0 +1,23 @@
+########################################################################
+#                        Coverage Configuration                        #
+#                                                                      #
+# This file controls the behavior of `cargo-tarpaulin`, which produces #
+# test coverage reports for the project.                               #
+########################################################################
+
+[coverage]
+all-features = true
+count = true
+ignore-panics = true
+ignore-tests = true
+run-types = [
+       "Tests",
+]
+
+[report]
+out = [
+       "Html",
+       "Lcov",
+       "Xml",
+]
+output-dir = "target/tarpaulin"