Expand description
Object Tapping
This crate provides traits for transparently inserting operations into a method chain. All traits take and return the object on which they act by value, and run a provided function on a borrow of the value.
This allows methods that do not chain (such as mutators with &mut self -> ()
signatures) to be chained.
The traits in this crate provide methods that run some function, Fn(&T)
or
Fn(&mut T)
, on a value T
without changing the binding status of that value.
Value Tapping
The primary trait of this crate is Tap
, which provides two methods: tap
and tap_mut
. These provide immutable or mutable, respectively, borrows of
the tapped value to a user-provided function. The user function must not have a
return value.
This permits using inspector-style (Fn(&Self)
) or mutator-style
(Fn(&mut Self)
) functions in a method chain without breaks or reduction of
access to the main value.
Tap methods never change the type of the object on which they are called. The
mut
-suffixed methods are permitted to change the value of their object.
Trait Tapping
Rust does not have subtyping in the object-oriented sense; rather, it uses traits to indicate relationships between types and bring behavior of an interior type to the exterior type. This crate provides taps that use the standard conversion traits in order to assist in running tap methods generically.
Borrowed Tapping
The traits std::borrow::Borrow
and std::borrow::BorrowMut
allow container
types to behave as their contained types in borrowed contexts. The TapBorrow
trait provides methods, tap_borrow
and tap_borrow_mut
, which depend on
Borrow
and BorrowMut
, respectively, to run the user-provided function on the
borrowed interior type.
This is useful for inspecting the interior of a Cow
or other data structures
that abstract away the exact container type but provide uniform access to the
underlying data.
Polymorphic Tapping
The traits std::convert::AsRef
and std::convert::AsMut
allow composed types
to be used by reference as their component types. The TapAsRef
trait
provides methods, tap_ref
and tap_ref_mut
, which depend on AsRef
and
AsMut
, respectively, to run the user-provided function on the referred
component type.
This is useful for working with types like Path
, which are commonly used as
generic targets such as <P: AsRef<Path>>
. All such types P
may have
.tap_ref
called upon them with methods implemented on Path
.
Note:
Borrow
andAsRef
are generic traits, which a type can implement many times with different targets. As such, the referent type must be specified in the tapped function. This can be done with a named method, or by marking the type of the closure argument:|x: &Referent| ...
.
Dereferenced Tapping
The traits std::ops::Deref
and std::ops::DerefMut
may be used to make owning
containers transparently defer to their contained data. This is used by Vec
and String
, for example, to behave like [T]
and str
implicitly.
The TapDeref
trait provides tap_deref
and tap_deref_mut
which call
Deref
or DerefMut
, respectively, on the tapped value before running the
provided function on the produced Deref::Target
value.
Since Deref
may only be implemented once, this trait does not require any
extra type information in its tap calls.
Conditional Tapping
Additional traits are provided to only invoke the tap when certain conditions are met in the value being tapped.
Boolean Tapping
The [TapBool
] trait, with methods [tap_true
], [tap_false
], and their
associated _mut
variants, run the provided function only when the value is of
the correct variant. This trait is implemented on bool
by default, and is
left open so that user crates may implement it on their own bool
-like types.
Optional Tapping
The TapOption
trait, with methods tap_some
, tap_some_mut
, and
tap_none
, run the provided function only when the Option
is of the
matching variant. The tap_some
methods pass &T
or &mut T
to their
function; tap_none
passes nothing.
Note that tap_some_mut
may change the value of the inner object, but it cannot
change the Option
from Some
to None
. If this behavior is desired, use
tap_mut
to modify the Option
wrapper directly, rather than tap_some_mut
to
change the interior value.
Result Tapping
This acts exactly like TapOption
, except that the alternate case has a value
that may be modified. It thus has methods tap_ok
, tap_err
, and the
associated _mut
variants.
Debug Tapping
All methods in the crate have a sibling method with the exact same name and
signature, except that the name is suffixed with _dbg
. This method runs the
normal tap in a debug build, and is removed in release builds.
use wyz::tap::TapOption;
Some(5i32).tap_some_dbg(|n| debug!("{}", n));
This emits a debug trace when the crate is built in debug mode, and does nothing when the crate is built in release mode.
Usage
Import the trait or traits you wish to use, with use wyz::tap::Tap;
, and then
attach .tap
methods on the end of any expression you want to inspect or
modify. These methods never change the type or binding status of the object to
which they are attached, and can be added or removed without affecting
neighboring code.
Examples
This uses tap_mut
to modify a vector using methods that cannot be chained, and
without converting to an iterator and re-collecting.
use wyz::tap::Tap;
let v = vec![5, 1, 2, 4, 3]
.tap_mut(|v| v.sort())
.tap_mut(|v| v.iter_mut().for_each(|e| *e *= 2))
.tap_mut(|v| v.reverse());
assert_eq!(&v, &[10, 8, 6, 4, 2]);
This uses tap_some
to implement a conditional flag.
use wyz::tap::TapOption;
let mut flag = false;
let n = None::<i32>.tap_some(|_| flag = true);
assert!(n.is_none());
assert!(!flag);
let n: Option<i32> = Some(1).tap_some(|_| flag = true);
assert_eq!(n.unwrap(), 1);
assert!(flag);
And this uses tap_err
to log errors without suppressing them.
use wyz::tap::TapResult;
let mut err_ct = 0;
{
let mut action = |e: &&str| {
err_ct += 1;
eprintln!("ERROR: {}", e);
};
Ok::<_, &str>("success").tap_err(&mut action);
Err::<(), _>("failure").tap_err(&mut action);
} // I didn't want to write the closure twice
assert_eq!(err_ct, 1);
// printed "ERROR: failure"
!