pipe{i}
pipei provides a zero-cost, type-safe way to chain multi-argument functions using method syntax. It turns a function call f(x, y, z) into a method call x.pipe(f)(y, z).
It also includes a tap operator for side-effects (logging, mutation) that returns the original value.
This project is inspired by the UMCS proposal.
It generalizes the tap crate to support multi-argument pipelines.
Note: Requires #![feature(impl_trait_in_assoc_type)] on nightly.
To optimize compile times, enable only the arities you need (from 0 up to 50).
Use features like up_to_N (where N is a multiple of 5) or specific individual arity features
[]
= "*" # default: features = ["up_to_5"]
# pipei = { version = "*", features = ["up_to_20"] }
# pipei = { version = "*", features = ["0", "1", "3", "4"] }
Basic chaining
pipe passes the value into the function and returns the result. tap inspects or mutates the input, ignores the result, and returns the original value.
use ;
let maybe_num = 2
.pipe
.pipe
.pipe
.pipe;
assert_eq!;
let val = 2
.tap // Immutable: passes &i32
.tap // Mutable: passes &mut i32
assert_eq!;
Partial Application
pipe can pre-fill the first argument of a function, creating a standalone, reusable function that accepts the remaining arguments.
use Pipe;
let season_pass = Discount ;
// Equivalent to the (hypothetical): let apply_discount = season_pass.apply;
let apply_discount = season_pass.pipe;
let prices = ;
let discounted = prices.map;
assert_eq!;
TapWith
Runs a side-effect on a projection of the value, which then returns the original value.
This is useful for reusing existing functions on a derived value (such as a field).
The side-effect executes only if the projection returns Some, which enables conditional flows (like tap_ok) and debug-only operations.
use TapWith;
let mut req = Request ;
// tap_mut on a field
.tap_with;
// tap_err (only tap on error)
let res = Err::
.tap_with;
assert_eq!;
// tap_dbg (only tap in debug mode)
let final_req = req.tap_with;
assert_eq!;
Comparison with the tap crate
pipei generalizes tap to support multi-argument functions, reducing syntactic noise and simplifying control flow when pipelines involve Result or Option types.
Standard Rust:
The reading order is inverted ("inside-out"), as save is written first, but executes last.
save;
Using tap:
Since ? applies to the closure, the closure itself returns a Result.
This forces manual Ok wrapping and an extra ? after the pipe.
load?
.pipe?
.pipe;
Using pipei:
The flow remains flat and ? works naturally.
load?
.pipe
.pipe;