Skip to content

Commit 50ba05e

Browse files
KSXGitHubclaude
andauthored
docs(guide/dev): pipe-trait usage (#361)
Added comprehensive documentation for the `pipe-trait` crate usage patterns to the CONTRIBUTING.md file, establishing clear guidelines for when and how to use the `Pipe` trait in the codebase. https://claude.ai/code/session_01DQYEAgJo7PnTgu9ejF1N1p --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 291f43d commit 50ba05e

4 files changed

Lines changed: 101 additions & 0 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
99
- Prefer merged imports
1010
- Use descriptive generic names (`Size`, `Report`), not single letters
1111
- Use descriptive variable and closure parameter names by default — single letters are only allowed in: conventional names (`n` for count, `f` for formatter), comparison closures (`|a, b|`), trivial single-expression closures, fold accumulators, index variables (`i`/`j`/`k` in short closures or index-based loops only), and test fixtures (identical roles only). Never use single letters in multi-line functions or closures
12+
- Use `pipe-trait` for chaining through unary functions (constructors, `Some`, `Ok`, free functions, etc.), avoiding nested calls, and continuing method chains — but not for simple standalone calls (prefer `foo(value)` over `value.pipe(foo)`)
1213
- Prefer `where` clauses for multiple trait bounds
1314
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
1415
- Custom errors: `#[derive(Debug, Display, Error)]`

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
99
- Prefer merged imports
1010
- Use descriptive generic names (`Size`, `Report`), not single letters
1111
- Use descriptive variable and closure parameter names by default — single letters are only allowed in: conventional names (`n` for count, `f` for formatter), comparison closures (`|a, b|`), trivial single-expression closures, fold accumulators, index variables (`i`/`j`/`k` in short closures or index-based loops only), and test fixtures (identical roles only). Never use single letters in multi-line functions or closures
12+
- Use `pipe-trait` for chaining through unary functions (constructors, `Some`, `Ok`, free functions, etc.), avoiding nested calls, and continuing method chains — but not for simple standalone calls (prefer `foo(value)` over `value.pipe(foo)`)
1213
- Prefer `where` clauses for multiple trait bounds
1314
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
1415
- Custom errors: `#[derive(Debug, Display, Error)]`

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
99
- Prefer merged imports
1010
- Use descriptive generic names (`Size`, `Report`), not single letters
1111
- Use descriptive variable and closure parameter names by default — single letters are only allowed in: conventional names (`n` for count, `f` for formatter), comparison closures (`|a, b|`), trivial single-expression closures, fold accumulators, index variables (`i`/`j`/`k` in short closures or index-based loops only), and test fixtures (identical roles only). Never use single letters in multi-line functions or closures
12+
- Use `pipe-trait` for chaining through unary functions (constructors, `Some`, `Ok`, free functions, etc.), avoiding nested calls, and continuing method chains — but not for simple standalone calls (prefer `foo(value)` over `value.pipe(foo)`)
1213
- Prefer `where` clauses for multiple trait bounds
1314
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
1415
- Custom errors: `#[derive(Debug, Display, Error)]`

CONTRIBUTING.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,104 @@ pub enum RuntimeError {
210210
}
211211
```
212212

213+
### Using `pipe-trait`
214+
215+
This codebase uses the [`pipe-trait`](https://docs.rs/pipe-trait) crate extensively. The `Pipe` trait enables method-chaining through unary functions, keeping code in a natural left-to-right reading order. Import it as `use pipe_trait::Pipe;`.
216+
217+
Any callable that takes a single argument works with `.pipe()` — free functions, closures, newtype constructors, enum variant constructors, `Some`, `Ok`, `Err`, trait methods like `From::from`, etc. The guidance below applies equally to all of them.
218+
219+
#### When to use pipe
220+
221+
**Chaining through a unary function at the end of an expression chain:**
222+
223+
```rust
224+
// Good — pipe keeps the chain flowing left-to-right
225+
stats.ino().pipe(InodeNumber)
226+
list.into_sorted_unstable_by_key(|entry| u64::from(entry.ino))
227+
.pipe(Reflection)
228+
value.0.into_iter().collect::<HashSet<_>>().pipe(Reflection)
229+
METRIC.parse_value(bytes).pipe(Output::Units)
230+
```
231+
232+
**Avoiding deeply nested function calls:**
233+
234+
```rust
235+
// Nested calls are harder to read
236+
let data = serde_json::from_reader::<_, JsonData>(stdin());
237+
let name = Some(OsStringDisplay::from(entry.file_name()));
238+
239+
// Prefer piping instead
240+
let data = stdin().pipe(serde_json::from_reader::<_, JsonData>);
241+
let name = entry.file_name().pipe(OsStringDisplay::from).pipe(Some);
242+
```
243+
244+
**Chaining through multiple unary functions:**
245+
246+
```rust
247+
// Good — clear wrapping pipeline
248+
ino.pipe(ConversionError::DuplicatedInode).pipe(Err)
249+
map.pipe(HardlinkList).pipe(Ok)
250+
251+
UnsupportedFeature::DeduplicateHardlink
252+
.pipe(RuntimeError::UnsupportedFeature)
253+
.pipe(Err)
254+
```
255+
256+
**Continuing a method chain through a free function and back to methods:**
257+
258+
```rust
259+
// Good — pipe bridges from methods to a free function and back
260+
block_dev
261+
.pipe(validate_block_device::<Fs>)
262+
.map(Cow::Borrowed)
263+
264+
"/sys/block"
265+
.pipe(Path::new)
266+
.join(block_dev)
267+
.pipe_as_ref(Fs::path_exists)
268+
.then_some(block_dev)
269+
```
270+
271+
**Using `.pipe_as_ref()` to pass a reference mid-chain** — avoids introducing a temporary variable when a free function takes `&T`:
272+
273+
```rust
274+
// Good — pipe_as_ref calls .as_ref() then passes to the function
275+
path_buf.pipe_as_ref(Fs::path_exists)
276+
277+
// Without pipe, you'd need a temporary or nested call
278+
Fs::path_exists(path_buf.as_ref())
279+
```
280+
281+
#### When NOT to use pipe
282+
283+
**Simple standalone function calls** — pipe adds noise with no readability benefit:
284+
285+
```rust
286+
// Bad — unnecessary pipe
287+
let result = value.pipe(foo);
288+
289+
// Good — just call the function directly
290+
let result = foo(value);
291+
```
292+
293+
This applies to any unary callable — `Some`, `Ok`, constructors, etc. — when there is no preceding chain to continue:
294+
295+
```rust
296+
// Bad — pipe adds nothing here
297+
let result = value.pipe(Some);
298+
299+
// Good — direct call is clearer
300+
let result = Some(value);
301+
```
302+
303+
However, piping through any unary function **is** preferred when it continues an existing chain:
304+
305+
```rust
306+
// Good — continues a chain
307+
report.summarize().pipe(Some)
308+
entry.file_name().pipe(OsStringDisplay::from).pipe(Some)
309+
```
310+
213311
### Pattern Matching
214312

215313
When mapping enum variants to values, prefer the concise wrapping style:

0 commit comments

Comments
 (0)