tap/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
/*! # `tap` – Syntactical Plumb-Lines
Rust permits functions that take a `self` receiver to be written in “dot-call”
suffix position, rather than the more traditional prefix-position function call
syntax. These functions are restricted to `impl [Trait for] Type` blocks, and
functions anywhere else cannot take advantage of this syntax.
This crate provides universally-implemented extension traits that permit smooth
suffix-position calls for a handful of common operations: transparent inspection
or modification (tapping), transformation (piping), and type conversion.
## Tapping
The [`tap`] module provides the [`Tap`], [`TapOptional`], and [`TapFallible`]
traits. Each of these traits provides methods that take and return a value, and
expose it as a borrow to an effect function. They look like this:
```rust
use tap::prelude::*;
# struct Tmp;
# fn make_value() -> Tmp { Tmp }
# impl Tmp { fn process_value(self) {} }
# macro_rules! log { ($msg:literal, $val:ident) => {{}}; }
let end = make_value()
.tap(|v| log!("Produced value: {:?}", v))
.process_value();
```
These methods are `self -> Self`, and return the value they received without
any transformation. This enables them to be placed anywhere in a larger
expression witohut changing its shape, or causing any semantic changes to the
code. The effect function receives a borrow of the tapped value, optionally run
through the `Borrow`, `AsRef`, or `Deref` view conversions, for the duration of
its execution.
The effect function cannot return a value, as the tap is incapable of handling
it.
## Piping
The [`pipe`] module provides the [`Pipe`] trait. This trait provides methods
that take and transform a value, returning the result of the transformation.
They look like this:
```rust
use tap::prelude::*;
struct One;
fn start() -> One { One }
struct Two;
fn end(_: One) -> Two { Two }
let val: Two = start().pipe(end);
// without pipes, this would be written as
let _: Two = end(start());
```
These methods are `self -> Other`, and return the value produced by the effect
function. As the methods are always available in suffix position, they can take
as arguments methods that are *not* eligible for dot-call syntax and still place
them as expression suffices. The effect function receives the piped value,
optionally run through the `Borrow`, `AsRef`, or `Deref` view conversions, as
its input, and its output is returned from the pipe.
For `.pipe()`, the input value is *moved* into the pipe and the effect function,
so the effect function *cannot* return a value whose lifetime depends on the
input value. The other pipe methods all borrow the input value, and may return a
value whose lifetime is tied to it.
## Converting
The [`conv`] module provides the [`Conv`] and [`TryConv`] traits. These provide
methods that accept a type parameter on the method name, and forward to the
appropriate `Into` or `TryInto` trait implementation when called. The difference
between `Conv` and `Into` is that `Conv` is declared as `Conv::conv::<T>`, while
`Into` is declared as `Into::<T>::into`. The location of the destination type
parameter makes `.into()` unusable as a non-terminal method call of an
expression, while `.conv::<T>()` can be used as a method call anywhere.
```rust,compile_fail
let upper = "hello, world"
.into()
.tap_mut(|s| s.make_ascii_uppercase());
```
The above snippet is illegal, because the Rust type solver cannot determine the
type of the sub-expression `"hello, world".into()`, and it will not attempt to
search all available `impl Into<X> for str` implementations to find an `X` which
has a
`fn tap_mut({self, &self, &mut self, Box<Self>, Rc<Self>, Arc<Self>}, _) -> Y`
declared, either as an inherent method or in a trait implemented by `X`, to
resolve the expression.
Instead, you can write it as
```rust
use tap::prelude::*;
let upper = "hello, world"
.conv::<String>()
.tap_mut(|s| s.make_ascii_uppercase());
```
The trait implementation is
```rust
pub trait Conv: Sized {
fn conv<T: Sized>(self) -> T
where Self: Into<T> {
self.into()
}
}
```
Each monomorphization of `.conv::<T>()` expands to the appropriate `Into<T>`
implementation, and does nothing else.
[`Conv`]: conv/trait.Conv.html
[`Pipe`]: pipe/trait.Pipe.html
[`Tap`]: tap/trait.Tap.html
[`TapFallible`]: tap/trait.TapFallible.html
[`TapOptional`]: tap/trait.TapOptional.html
[`TryConv`]: conv/trait.TryConv.html
[`conv`]: conv/index.html
[`pipe`]: pipe/index.html
[`tap`]: tap/index.html
!*/
#![no_std]
#![cfg_attr(debug_assertions, warn(missing_docs))]
#![cfg_attr(not(debug_assertions), deny(missing_docs))]
pub mod conv;
pub mod pipe;
pub mod tap;
/// Reëxports all traits in one place, for easy import.
pub mod prelude {
#[doc(inline)]
pub use crate::{conv::*, pipe::*, tap::*};
}
// also make traits available at crate root
#[doc(inline)]
pub use prelude::*;