- Notifications
You must be signed in to change notification settings - Fork17
Generic extensions for tapping values in Rust.
License
myrrlyn/tap
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This crate provides extension methods on all types that allow transparent,temporary, inspection/mutation (tapping), transformation (piping), or typeconversion. These methods make it convenient for you to insert debugging ormodification points into an expression without requiring you to change any otherportions of your code.
You can tap inside a method-chain expression for logging without requiring arebind. For instance, you may write a complex expression without anyintermediate debugging steps, and only later decide that you want them.Ordinarily, this transform would look like this:
externcrate reqwest;externcrate tracing;// oldlet body = reqwest::blocking::get("https://example.com")?.text()?;tracing::debug!("Response contents: {}", body);// new, with debugginglet 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 overallexpression, without making any other changes:
externcrate reqwest;externcrate 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);
Some APIs are written to require mutable borrows, rather than value-to-valuetransformations, which can require temporary rebinding in order to createmutability in an otherwise-immutable context. For example, collecting data intoa vector, sorting the vector, and then freezing it, might look like this:
letmut collection =stream().collect::<Vec<_>>();collection.sort();// potential error site: inserting other mutations herelet collection = collection;// now immutable
But with a mutable tap, you can avoid the duplicate bindingand guard againstfuture 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 withoutexposing the intermediate permission.
In addition to transparent inspection or modification points, you may also wishto use suffix calls for subsequent operations. For example, the standard libraryoffers the free functionfs::read
to convertPath
-like objects intoVec<u8>
of their filesystem contents. Ordinarily, free functions require useas:
use std::fs;letmut 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 (forfs::read
) couldbe 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 freefuntions withoutlet
-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, tounderstand the full sequence of evaluation. With suffix calls, even freefunctions can be written in a point-free style that maintains a clear temporaland 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 requiresthat you write function-call expressions when using a function with multiplearguments in the pipeline.
Theconv
module is the simplest: it provides two traits,Conv
andTryConv
,which are sibling traits toInto<T>
andTryInto<T>
. Their methods,Conv::conv::<T>
andTryConv::try_conv::<T>
, call the correspondingtrait implementation, and allow you to use.into()
/.try_into()
innon-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 intermediatelet
binding, youcan write it as
let bytes ="hello".conv::<String>().into_bytes();
TheTap
andPipe
traits both provide a large number of methods, which usedifferent parts of the Rust language’s facilities for well-typed value access.Rather than repeat the API documentation here, you should view the module itemsin thedocumentation.
As a summary, these traits provide methods that, upon receipt of a value,
- apply no transformation
- apply an
AsRef
orAsMut
implementation - apply a
Borrow
orBorrowMut
implementation - apply the
Deref
orDerefMut
implementation
before executing their effect argument.
In addition, eachTap
method.tap_x
has a sibling method.tap_x_dbg
thatperforms the same work, but only in debug builds; in release builds, the methodcall is stripped. This allows you to leave debugging taps in your source code,without affecting your project’s performance in true usage.
Lastly, thetap
module also has traitsTapOptional
andTapFallible
whichrun taps on the variants ofOption
andResult
enums, respectively, and donothing when the variant does not match the method name.TapOptional::tap_some
has no effect when called on aNone
, etc.
About
Generic extensions for tapping values in Rust.
Resources
License
Stars
Watchers
Forks
Packages0
Languages
- Rust100.0%