wyz 0.4.0

myrrlyn’s utility collection
Documentation

wyz

Crate Documentation License

Continuous Integration Code Coverage Crate Downloads Crate Size

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

  1. conv
  2. exit
  3. fmt
  4. pipe
  5. 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.

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.

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.

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:

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.

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.

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.