Generic extensions for tapping values in Rust.

Related tags

Command-line tap
Overview

tap

Suffix-Position Pipeline Behavior

Crate Documentation License

Crate Downloads Crate Size

This crate provides extension methods on all types that allow transparent, temporary, inspection/mutation (tapping), transformation (piping), or type conversion. These methods make it convenient for you to insert debugging or modification points into an expression without requiring you to change any other portions of your code.

Example Use

Tapping

You can tap inside a method-chain expression for logging without requiring a rebind. For instance, you may write a complex expression without any intermediate debugging steps, and only later decide that you want them. Ordinarily, this transform would look like this:

extern crate reqwest;
extern crate tracing;

// old
let body = reqwest::blocking::get("https://example.com")?
  .text()?;
tracing::debug!("Response contents: {}", body);

// new, with debugging
let resp = reqwest::blocking::get("https://example.com")?;
tracing::debug!("Response status: {}", resp.status());
let body = resp.text()?;
tracing::debug!("Response contents: {}", body);

while with tapping, you can plug the logging statement directly into the overall expression, without making any other changes:

extern crate reqwest;
extern crate tracing;

let body = reqwest::blocking::get("https://example.com")?
  // The only change is the insertion of this line
  .tap(|resp| tracing::debug!("Response status: {}", resp.status()))
  .text()?;
tracing::debug!("Response contents: {}", body);

Mutable Tapping

Some APIs are written to require mutable borrows, rather than value-to-value transformations, which can require temporary rebinding in order to create mutability in an otherwise-immutable context. For example, collecting data into a vector, sorting the vector, and then freezing it, might look like this:

let mut collection = stream().collect::<Vec<_>>();
collection.sort();
// potential error site: inserting other mutations here
let collection = collection; // now immutable

But with a mutable tap, you can avoid the duplicate binding and guard against future errors due to the presence of a mutable binding:

let collection = stream.collect::<Vec<_>>()
  .tap_mut(|v| v.sort());

The .tap_mut() and related methods provide a mutable borrow to their argument, and allow the final binding site to choose their own level of mutability without exposing the intermediate permission.

Piping

In addition to transparent inspection or modification points, you may also wish to use suffix calls for subsequent operations. For example, the standard library offers the free function fs::read to convert Path-like objects into Vec<u8> of their filesystem contents. Ordinarily, free functions require use as:

use std::fs;

let mut path = get_base_path();
path.push("logs");
path.push(&format!("{}.log", today()));
let contents = fs::read(path)?;

whereäs use of tapping (for path modification) and piping (for fs::read) could be expressed like this:

use std::fs;

let contents = get_base_path()
  .tap_mut(|p| p.push("logs"))
  .tap_mut(|p| p.push(&format!("{}.log", today())))
  .pipe(fs::read)?;

As a clearer example, consider the syntax required to apply multiple free funtions without let-bindings looks like this:

let val = last(
  third(
    second(
      first(original_value),
      another_arg,
    )
  ),
  another_arg,
);

which requires reading the expression in alternating, inside-out, order, to understand the full sequence of evaluation. With suffix calls, even free functions can be written in a point-free style that maintains a clear temporal and syntactic order:

let val = original_value
  .pipe(first)
  .pipe(|v| second(v, another_arg))
  .pipe(third)
  .pipe(|v| last(v, another_arg));

As piping is an ordinary method, not a syntax transformation, it still requires that you write function-call expressions when using a function with multiple arguments in the pipeline.

Conversion

The conv module is the simplest: it provides two traits, Conv and TryConv, which are sibling traits to Into<T> and TryInto<T>. Their methods, Conv::conv::<T> and TryConv::try_conv::<T>, call the corresponding trait implementation, and allow you to use .into()/.try_into() in non-terminal method calls of an expression.

let bytes = "hello".into().into_bytes();

does not compile, because Rust cannot decide the type of "hello".into(). Instead of rewriting the expression to use an intermediate let binding, you can write it as

let bytes = "hello".conv::<String>().into_bytes();

Full Functionality

The Tap and Pipe traits both provide a large number of methods, which use different parts of the Rust language’s facilities for well-typed value access. Rather than repeat the API documentation here, you should view the module items in the documentation.

As a summary, these traits provide methods that, upon receipt of a value,

  • apply no transformation
  • apply an AsRef or AsMut implementation
  • apply a Borrow or BorrowMut implementation
  • apply the Deref or DerefMut implementation

before executing their effect argument.

In addition, each Tap method .tap_x has a sibling method .tap_x_dbg that performs the same work, but only in debug builds; in release builds, the method call is stripped. This allows you to leave debugging taps in your source code, without affecting your project’s performance in true usage.

Lastly, the tap module also has traits TapOptional and TapFallible which run taps on the variants of Option and Result enums, respectively, and do nothing when the variant does not match the method name. TapOptional::tap_some has no effect when called on a None, etc.

Comments
  • please include a file with the license text in the git repo and in published crates

    please include a file with the license text in the git repo and in published crates

    There exist lots of variants of the "MIT" license. Without a license text in the repository, it is not clear which variant is applicable.

    For example, the Fedora project recognises over 20 different variants: https://fedoraproject.org/wiki/Licensing:MIT

    Please include a license text in this repo, and also add the file to the "include" array in Cargo.toml, so it will be included in the published crates as well.

    This is a requirement for applying MIT-style licenses correctly (GitHub license page headers show this, for example here, and it's mentioned on choosealicense.com as well). Most Rust projects licensed under the terms of an MIT license seem to have chosen the MIT License variant that's used by the linked GitHub example (bitvec), which the Fedora Project categorizes as "Modern Style with sublicense".

    opened by decathorpe 2
  • reexport prelude contents from crate root, and add #[doc(inline)]

    reexport prelude contents from crate root, and add #[doc(inline)]

    I was going to open an issue, but I realized it would be easier to just open a PR to show what I meant.

    For those of us that prefer named imports instead of glob imports, this makes it a little bit more convenient to do so. use tap::tap::Tap; is shortened to use tap::Tap;.

    Note I also added #[doc(inline)], so that the exported traits appear in the generated documentation at the crate root, instead of just seeing the glob import in the documentation. This is what it looks like now:

    Screen Shot 2020-11-21 at 3 04 07 PM

    Let me know what you think!

    opened by mikeyhew 1
  • Trial using Try with TapFallible

    Trial using Try with TapFallible

    DO NOT MERGE

    This is a test PR to trial using the new try_trait_v2 feature with TapFallible inspired by the note^1 in the TapOptional docs indicating that it was slated for removal once try is stabilized.

    opened by yaahc 0
  • add pipe_if method(s)

    add pipe_if method(s)

    Reading this comment on the rust internals forum got me interested in a conditional pipe method; to borrow the example from the post, it would simplify pipes with basic tests like this:

    Builder::new()
        .foo()
        .bar()
        .pipe(|it| if bazable {
            it.baz()
        } else {
            it
        })
        .quux()
        .build()
    

    Into this:

    Builder::new()
        .foo()
        .bar()
        .pipe_if(bazable, |it| it.baz())
        .quux()
        .build()
    

    I figure the implementations would be trivial, the main factor that I see getting in the way of this is that it could mean doubling the number of methods on Pipe, by adding _ref, _ref_mut, _deref etc variants.

    opened by Zoybean 0
  • Should tap allow the closure to return an ignorable value?

    Should tap allow the closure to return an ignorable value?

    I code this:

    use std::io::stdin;
    String::new().tap_mut(|s| stdin().read_line(s).unwrap())
    

    Getting error message:

    xx |     String::new().tap_mut(|s| stdin().read_line(s).unwrap())
       |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `usize`
    

    It can be solved by adding brace and semicolon to the closure, but that doesn't seem ergonomic enough.

    Do you think tap should allow the closure to return an ignorable value?

    opened by hzqd 1
  • Enrich descriptions and examples for `TapOptional` and `TapFallible`

    Enrich descriptions and examples for `TapOptional` and `TapFallible`

    Thank you for making this crate!

    I would like to merge my original descriptions and examples from my own crate respector into this one. Those new descriptions are based on the documentation of inspect.

    (What respector is meant to do is basically what TapOptional and TapFallible are doing right now. So that's why I'm planning to deprecate my crate in favor of yours.)

    BTW: I fixed several typos as well :)

    opened by rami3l 0
Owner
Alexander Payne
My Rust code is fast but my C++ code runs at 17,000mph.
Alexander Payne
Eventually consistent values for Rust

Eventuals give you the most up-to-date snapshots of some value. They are like Futures that update over time, continually resolving to an eventually co

Edge & Node 110 Dec 31, 2022
Rust TUI library - Clipping region is a set of min/max x/y values applied to the existing region

TinyBit Clipping region is a set of min/max x/y values applied to the existing region A TUI lib This is not yet production ready T O D O TODO: bugs: T

Togglebit 13 May 3, 2022
Microsoft Excel (XLSX) to Unicode Separated Values (USV) Rust crate

xlsx-to-usv Convert Microsoft Excel (XLSX) to Unicode Separated Values (USV). Built with the USV Rust crate. Syntax: stdin | xlsx-to-usv [options] | s

SixArm 3 Mar 31, 2024
Generic Differential Evolution for Rust

Differential Evolution Simple and powerful global optimization using a Self-Adapting Differential Evolution for Rust. See Wikipedia's article on Diffe

Martin Leitner-Ankerl 13 Apr 30, 2022
botwork is a single-binary, generic and open-source automation framework written in Rust for acceptance testing & RPA

botwork botwork is a single-binary, generic and open-source automation framework written in Rust for acceptance testing, acceptance test driven develo

Nitimis 8 Apr 17, 2023
Padding/aligning values without heap allocation

zero-copy-pads Padding/aligning values without heap allocation. Cargo Features std (default feature): Disable #![no_std]. Enable features that require

Khải 5 Nov 29, 2021
Quickly save and retrieve values for shell scripts.

Quickly save and retrieve values for shell scripts.

Alex Andrade 2 Dec 15, 2022
a crate to swap values between possibly-overlapping references

omniswap: a crate to swap values between possibly-overlapping references Motivating Example You cannot simply use std::mem::swap to replace values wit

Masaki Hara 21 Nov 30, 2022
🍅 A command-line tool to get and set values in toml files while preserving comments and formatting

tomato Get, set, and delete values in TOML files while preserving comments and formatting. That's it. That's the feature set. I wrote tomato to satisf

C J Silverio 15 Dec 23, 2022
Encode and decode dynamically constructed values of arbitrary shapes to/from SCALE bytes

scale-value · This crate provides a Value type, which is a runtime representation that is compatible with scale_info::TypeDef. It somewhat analogous t

Parity Technologies 15 Jun 24, 2023
A command line tool that resembles a debugger as well as Cheat Engine, to search for values in memory

Summary This is a small command-line tool designed to peek around memory of a running Linux process. It also provides filtering mechanisms similar to

null 213 Jul 4, 2023
Library uses file-based mmap to store key-values

Library uses file-based mmap to store key-values This is a Rust version of MMKV. By default, this lib uses CRC8 to check data integrity. If include fe

Kexuan Yang 4 Aug 14, 2023
Macro to print variable(s) with values nicely (stripped from release builds)

log_macro Macro to print variable(s) with values nicely (stripped from release builds) Install cargo add log_macro Use Add this to top of file: #[mac

Nikita 3 Aug 22, 2023
Use LLMs to generate strongly-typed values

Magic Instantiate Quickstart use openai_magic_instantiate::*; #[derive(MagicInstantiate)] struct Person { // Descriptions can help the LLM unders

Grant Slatton 4 Feb 20, 2024
A fast bump allocator that supports allocation scopes / checkpoints. Aka an arena for values of arbitrary types.

bump-scope A fast bump allocator that supports allocation scopes / checkpoints. Aka an arena for values of arbitrary types. What is bump allocation? A

null 7 May 4, 2024
a hack implementation of CCS generic arithmetization, won a prize at Zuzalu hackathon 2023 despite incompleteness

ccs-hack CCS (Customized Constraint System) is a generic constraints representation system can simultaneously capture R1CS, Plonkish, and AIR: $$\sum_

Thor 27 Jun 1, 2023
Asserts const generic expressions at build-time.

build_assert build_assert allows you to make assertions at build-time. Unlike assert and some implementations of compile-time assertions, such as stat

MaxXing 4 Nov 23, 2023
Rust-advent - Learning Rust by solving advent of code challenges (Streaming live on Twitch every Monday)

Rust advent ?? ?? Learning Rust by implementing solutions for Advent of Code problems. ?? HEY, we are live-streaming our attempts to solve the exercis

Luciano Mammino 20 Nov 11, 2022
Rust-clippy - A bunch of lints to catch common mistakes and improve your Rust code

Clippy A collection of lints to catch common mistakes and improve your Rust code. There are over 450 lints included in this crate! Lints are divided i

The Rust Programming Language 8.7k Dec 31, 2022