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::*;