# pipe{i}
_pipei_ allows writing `x.pipe(f)(y, z)` in place of `f(x, y, z)`, enabling method-style chaining and partial application for multi-argument functions.
It also provides `tap` and `tap_with` for multi-argument side effects that return the original value.
This project is inspired by the [UMCS (Universal Method Call Syntax) proposal](https://internals.rust-lang.org/t/weird-syntax-idea-s-for-umcs/19200/35). It requires nightly Rust for `#![feature(impl_trait_in_assoc_type)]`.
### Basic Chaining
`pipe` passes the value as the first argument to a function and returns the result.
`tap` passes the value to a function for a side effect, then returns the original value.
```rust
use pipei::{Pipe, Tap};
fn add(x: i32, y: i32) -> i32 { x + y }
let result = 2
.pipe(add)(3)
.pipe(|x, a, b| a * x + b)(10, 1)
.pipe(Option::Some)();
assert_eq!(result, Some(51));
fn log(x: &i32) { println!("val: {}", x); }
fn add_assign(x: &mut i32, y: i32) { *x += y; }
let val = 2
.tap(log)() // Immutable: inferred &i32
.tap(add_assign)(3); // Mutable: inferred &mut i32
assert_eq!(val, 5);
```
### Partial Application
Because `pipe` returns a closure over the remaining arguments, it doubles as partial application.
```rust
use pipei::Pipe;
struct Discount { rate: f64 }
impl Discount {
fn apply(&self, price: f64) -> f64 {
price * (1.0 - self.rate)
}
}
let season_pass = Discount { rate: 0.20 };
// Equivalent to (the hypothetical): let apply_discount = season_pass.apply;
let apply_discount = season_pass.pipe(Discount::apply);
let prices = [100.0, 200.0, 300.0];
let discounted = prices.map(apply_discount);
assert_eq!(discounted, [80.0, 160.0, 240.0]);
```
### Projected Side Effects
`tap_with` lets you reuse a side-effect function whose signature doesn't match the receiver. A projection extracts the relevant part; if it returns `Some`, the side effect runs on the projected value. If `None`, it is skipped. In both cases, the original value is returned.
```rust
# use pipei::TapWith;
struct Request { url: String, attempts: u32 }
fn track_retry(count: &mut u32) { *count += 1 }
fn log_trace<T: core::fmt::Debug>(val: &T, label: &str) { /* ... */ }
let mut req = Request { url: "https://pipei.rs".into(), attempts: 3 };
// track_retry expects &mut u32, not &mut Request — the projection bridges the gap
(&mut req).tap_with(|r| Some(&mut r.attempts), track_retry)();
assert_eq!(req.attempts, 4);
// tap only on Err
let res = Err::<(), _>(503)
.tap_with(|x| x.as_ref().err(), log_trace)("request failed");
assert_eq!(res.unwrap_err(), 503);
// tap only in debug builds
let req = req.tap_with(|r| {
#[cfg(debug_assertions)] { Some(r) }
#[cfg(not(debug_assertions))] { None }
}, log_trace)("FINAL");
assert_eq!(req.attempts, 4);
```
### Error Handling
The [`tap`](https://crates.io/crates/tap) crate provides single-argument `pipe` and `tap` traits.
_pipei_ generalizes both to multi-argument functions, which particularly simplifies fallible pipelines where `?` interacts poorly with closures.
**Standard Rust:**
The reading order is inverted ("inside-out"): `save` is written first, but executes last.
```rust
save(
composite_onto(
load("background.png")?,
resize(load("overlay.png")?, 50),
0.8
),
"result.png"
);
```
**Using _tap_:**
Since `?` applies inside the closure, the closure returns a `Result`, forcing manual `Ok` wrapping and an extra `?`.
```rust
load("background.png")?
.pipe(|bg| {
let overlay = load("overlay.png")?
.pipe(|fg| resize(fg, 50));
Ok(composite_onto(bg, overlay, 0.8))
})?
.pipe(|img| save(img, "result.png"));
```
**Using _pipei_:**
```rust
load("background.png")?
.pipe(composite_onto)(
load("overlay.png")?.pipe(resize)(50),
0.8,
)
.pipe(save)("result.png");
```
### Feature Flags
To optimize compile time, enable only the arities you need (from 0 up to 50).
Use `up_to_N` features (available in multiples of five) or enable individual arity features.
```toml
[dependencies]
pipei = "*" # default: features = ["up_to_5"]
# pipei = { version = "*", features = ["up_to_20", "31"] }
# pipei = { version = "*", features = ["0", "1", "3", "4"] }
```