pub struct SupervisorConfig {Show 14 fields
pub ssas_per_request: usize,
pub allow_dynamic_ssa_batches: bool,
pub max_failed_cycles: usize,
pub max_ssa_delivery_time: Duration,
pub commitment_recommit_interval: Duration,
pub max_deposit_wait: Duration,
pub max_recovery_idle: Duration,
pub max_recovery_time: Duration,
pub max_off_front_share_fraction: f64,
pub min_share_order_sample: u64,
pub max_predeposit_packets: u64,
pub max_served_without_progress: u64,
pub tombstone_retention_window: Duration,
pub fill: PixFillConfig,
}Expand description
Configuration for the SessionPixSupervisor.
Reachable from a node’s configuration file as
incoming_session_pix_config.supervision. The fields interact — with each other and with the
reconstructor’s own lifetimes — so a set of them is only meaningful as a whole; that is what
validate_pix_supervision checks, and the node’s config validator runs it at load time.
A worked set of values, and what each field defends against, is written out under
“Configuration” in the supervision module documentation — the module is crate-private, so it is
named rather than linked here.
Fields§
§ssas_per_request: usizeMaximum number of SSAs the Exit may ask the Entry to commit to in one SsaRequest.
With allow_dynamic_ssa_batches, Session admission picks
the smallest batch no larger than this value whose total quota is inside the Exit’s accepted
range. With dynamic batching disabled, this remains the exact batch size, preserving the
original fixed-batch behavior. Batching amortizes the request round trip over several deposit
cycles. It lives here rather than beside the other Exit settings because the supervisor acts
on the selected value: it allocates the indices and scales both deadlines below by this factor.
Two things it costs, both linear in the value:
- Up to two batches can own live reconstructor state — worst case ≈41 MiB per cycle at the profiled dimensions,
per
hopr_protocol_pix::peak_cycle_bytes. That cost is reserved up front againstIncomingSessionPixConfig::max_live_cycle_bytes; the supervisor enforces the two-generation/full-cycle bound before allocating, so this value directly divides the number of PIX Sessions the node will admit. - The unfunded exposure within one batch: every cycle is initially requested unfunded, so the commitment and deposit deadlines cover this many SSA quotas rather than one. The front-aware service gate still grants only the current cycle’s bounded predeposit allowance.
Must not exceed the peer Entry’s max_ssas_per_request. The batch size is not negotiated
— the options word in the most significant 64 bits of StartSession.additional_data is fully
allocated (PIX dimensions and SURB balancer target), and its least significant 64 bits are not
yet used for this, so the Entry does not advertise its cap and the Exit cannot learn it. An Entry that finds a
batch too large refuses it in full and replies with an UnacceptablePixParams SessionError, which closes
the Session on both sides in about a round trip. Every such Session is still lost; the reply only makes the
failure immediate and attributable instead of surfacing as a commitment timeout.
Range-checked by validate_pix_supervision and clamped where it is read, so it can never
exceed MAX_SSA_BATCH_SIZE.
Default: 1, which reproduces the unbatched exchange byte-for-byte.
allow_dynamic_ssa_batches: boolWhether Session admission may derive a smaller SSA batch from the Entry’s offered quota.
When enabled, the Exit tries batch sizes from one through
ssas_per_request and selects the first whose total quota is inside
IncomingSessionPixConfig::quota_range. If none fits, the Session is refused with
UnacceptablePixParams. Choosing the smallest fit limits Entry work, deposits, Exit memory,
and the chance of exceeding the Entry’s unadvertised max_ssas_per_request cap.
When disabled, the offered per-SSA quota itself must be inside quota_range, and the Exit
requests exactly ssas_per_request SSAs, matching the behavior before dynamic batching.
Default: true.
max_failed_cycles: usizeCycles this Session may lose without recovering them before it is closed. The next one closes it.
Counted over the whole Session, not per batch, because a retired cycle leaves no trace on its siblings: without a cumulative count an Entry can lose one cycle per batch forever while a single funded sibling keeps the Session alive, paying for a fraction of what it is served.
Only reachable with ssas_per_request > 1. At the shipping batch
of one the failing cycle is always the last one standing, and that closes the Session before
this is ever consulted — so nothing changes at the defaults.
A recovered cycle never counts: it leaves through tombstone expiry, not through the failure path.
Default: 1 — one lost cycle is survivable, which is what the retire-and-hand-on-the-successor -gate path exists for, and a second is not.
max_ssa_delivery_time: DurationMaximum time to wait for the SSA to be fully committed.
Together with max_deposit_wait this bounds how long a Session may
be served unincentivized: the Entry has to be able to deliver its SSA commitment before it
can be asked to pay for it.
Multiplied by ssas_per_request when the deadline is armed: a batch
asks the Entry for that many commitment sets in answer to one request, and all of their clocks
start together, so holding each to a single cycle’s budget would fail an Entry that is
answering in order.
Default: 20 s.
commitment_recommit_interval: DurationHow long a partially delivered commitment may go quiet before the missing parts are re-requested.
The Entry’s answer to an SsaRequest is not one message but hundreds of them, and the Exit
can use none of it until every one has landed — so a single lost packet used to cost the whole
Session on max_ssa_delivery_time, typically after the Entry
had already been told to fund the cycle’s deposit address. This is the timer that repairs it:
on expiry the Exit asks the Entry to re-send the polynomial commitments it never received.
An idle timer, armed and re-armed by each arriving commitment message, so it measures silence rather than elapsed time and the first ask cannot land in the middle of the burst. It is only ever armed once something has arrived: a commitment nothing was delivered for has no scope to name, and there is nothing the Exit could ask for.
It bounds nothing on its own — max_ssa_delivery_time remains the absolute deadline, so the
unincentivized exposure of a cycle is unchanged however often this fires. How many asks a
cycle makes is bounded by count rather than by this interval:
MAX_COMMITMENT_RETRANSMISSIONS, which
is also exactly how many the Entry will answer, so shortening this buys promptness and never
a flood of wasted packets.
Default: 3 s — well clear of the time a full burst takes to arrive, and short enough that a repair completes several times over inside the delivery deadline.
max_deposit_wait: DurationMaximum time to wait for a deposit after the commitment is verifiable.
Also multiplied by ssas_per_request. Each cycle’s clock starts at
its own commitment, which staggers them somewhat — but a batch’s commitments arrive
back-to-back while an Entry funding them serially finishes the last one that many deposits
later, so the stagger does not cover it.
Default: 60 s.
max_recovery_idle: DurationMaximum idle time during recovery when service is being consumed.
Gated on service consumption — if no packets were served, the timer re-arms instead of closing.
Default: 60 s.
max_recovery_time: DurationAbsolute per-SSA recovery deadline (immutable once set).
This is a resource backstop (session slot + reconstructor memory), not the anti-drip mechanism. The service-gated idle rule is.
§Why four hours
It has to leave a whole cycle at the dimensions the node accepts time to complete, but that
floor is far below four hours: ~61 min with fill at its default ceiling, ~2 min without fill
at MAX_ASSUMED_SESSION_PACKET_RATE. A cycle cut
short is worth nothing, since the SSA is the sum of every polynomial’s constant term, so the
margin is cheap.
What sets the value is the rest of what it does. With fill enabled it is the idle tariff: an
idle Session’s cycle is finished at finish_fraction of it, three hours, which needs 64
packets/s for the widest accepted quota. And it keeps this instrument far enough out that
max_recovery_idle is what actually binds.
The clock starts when the cycle reaches the paid transport front. That is deliberately later than merely becoming the earliest unrecovered record: the predecessor’s buffered surplus tail can still occupy ~4096 packets, over a minute for a slow Session, and remains bounded by the predecessor’s original hard clock. Starting the successor at the FIFO boundary prevents that pipeline delay from being charged twice. A funded cycle that reaches the front and is never served is still caught.
HoprProtocolConfig::validate checks this against the dimensions the node will actually
accept, so a raised quota_range that outgrows it is refused at load rather than discovered
one closed Session at a time.
Default: 4 hours.
Largest share of recovery progress that may land on a cycle other than the one at the front of the batch, as a fraction of all progress since that cycle took the front.
§What it defends
A batch only de-risks the Exit if its cycles are served one at a time, so that recovering the
first pays for the traffic that earned it. An Entry that instead spread a batch’s shares across
all ssas_per_request cycles could take ssas_per_request × quota of service while leaving
every cycle short of recovery — and a cycle short of recovery pays nothing at all, since the SSA
is the sum of every polynomial’s constant term. That turns the batch knob from cost-neutral
into an n-fold amplifier of unpaid traffic.
Nothing else sees it. The service gate and max_recovery_idle both
measure the absence of progress, and a spreading Entry makes progress on every cycle
continuously — including the front one, whose idle timer it therefore keeps refreshing. It
simply never finishes one.
§Why a fraction and not a count
Out-of-order arrival is not an Entry property. SURBs cross the mixnet outbound and their
acknowledgements cross it again inbound, so arrival order is a permutation of emission order
whose width is roughly delay_range × packet rate. An absolute tolerance would therefore have
to be re-derived every time the mixer is reconfigured, and — being cumulative — would drift
into closing honest Sessions given enough cycle boundaries.
A ratio has neither problem: a permutation of bounded width is a vanishing fraction of a
growing window, so widening delay_range cannot move it, and the denominator grows with the
numerator so nothing accumulates. It is also independent of packet rate (both terms scale
together, so unlike a wall-clock bound it implies no throughput floor), of loss (which hits
every cycle alike), and of surplus_shares — which matters even now that the surplus is
negotiated rather than Entry-private. The Exit knows the budget, not the spend: how many of a
cycle’s emitted shares turn out to be useful depends on the loss the Entry actually met, so no
expected shares-per-packet ratio follows from the parameter. Comparing the batch’s cycles
against each other cancels it.
§The value
A conforming Entry sits at ~0: with emission clamped to one cycle
(hopr_protocol_pix::SHARE_EMISSION_WINDOW) the only off-front progress is boundary
reordering. Spreading across n cycles gives (n − 1) / n — 0.5 at two cycles, 0.67 at three.
0.25 separates those with room on both sides.
It bounds rather than eliminates: an Entry may divert up to this fraction and stay legal, so
worst-case unpaid exposure becomes ~1 / (1 − f) cycles instead of n. Alternating — serving
the front cycle honestly, then dumping on later ones — buys nothing, because the accounting only
resets when the front cycle completes, which means it was paid for.
Default: 0.25.
Progress that must accumulate before
max_off_front_share_fraction is judged at all, in useful
shares.
A sample-size floor, not a tolerance: it does not bound how much a cheat can extract — the fraction does that — it only refuses to convict on thin evidence.
Sized by the one case that would otherwise produce a false positive: a cycle made
unrecoverable by loss. A polynomial that loses more than surplus_shares of its shares can
never reach its threshold, so its cycle stops progressing while later ones continue, and the
off-front fraction climbs to 1.0. That cycle is not something to close a Session over — it stops
progressing, its service-gated max_recovery_idle deadline expires,
it is retired, and the accounting resets on the new front. The sample floor only has to outlast
that window: ~181 shares/s, the per-Session rate this was sized at, over 60 s is ~10 900, and
16 384 covers up to ~273 shares/s.
Unlike an absolute tolerance this does not have to grow with Session length or track the mixer configuration — it is a floor on evidence, evaluated once and then continuously.
Very large values can make this rule effectively inert for a Session. To disable share-order
enforcement intentionally, prefer
max_off_front_share_fraction = 1.0. No fixed upper bound
is enforced because the accounting window can span multiple successor batches before the front
advances.
Default: 16384 shares.
max_predeposit_packets: u64Cap on the provisional predeposit service budget.
This buys the application an exchange while the current front cycle’s deposit confirms on chain; it is not needed for the Session to become fundable, so it is a latency-versus-exposure dial rather than a correctness requirement. The initial front starts with this allowance, and it is restored for an unfunded successor after a paid front leaves the front, whether by recovery or a tolerated failure. Retiring an unfunded front does not grant it again.
Zero is supported and means strict prepay: the Exit answers nothing until a sufficient
deposit is confirmed. Everything on the path to funding — the SsaRequest, the Entry’s
commitment, the deposit itself, and the SURB keep-alive stream — bypasses the egress gate, so
a zero budget stalls the application without ever stalling the funding handshake. Deliberately
not rejected by validate_pix_supervision for that reason, unlike
max_served_without_progress, where zero would wedge a
Session that has already paid.
The effective budget is min(target_useful_shares - 1, max_predeposit_packets), so this value
is the binding one at any realistic set of PIX dimensions.
Default: 10000 packets.
max_served_without_progress: u64Maximum packets served without a single share coming back, before the gate blocks further service as a defence-in-depth backstop.
A ceiling enforced by ServiceGate::acquire while the current front is funded. A
crate::HoprSessionInPixEvent::RecoveryProgress snapshot resets it only for that funded
front. The sole recovered predecessor temporarily remains that paid front while its
per-polynomial negotiated remainder drains; progress on an unfunded, queued, older, or
over-budget cycle does not grant service.
Those events now follow shares_seen rather than useful_shares, which is what makes a flat
2048 safe at any dimensions. Keyed on useful shares, this had to exceed
surplus x min(polys, SHARE_EMISSION_WINDOW) — 4096 at the shipped dimensions, twice
this value — because a conforming Entry emits a whole window’s surplus in one contiguous run
during which no share is useful. The gate parked the writer partway through, and a parked
writer spends no SURBs, so nothing could arrive to release it. See
SsaRecoveryProgress::shares_seen.
What it bounds now is genuine silence, and that is rate-independent: it is a count of
packets, so unlike max_recovery_idle it implies no throughput
floor.
Default: 2048 packets.
tombstone_retention_window: DurationHow long to retain recovered-SSA diagnostic records and observer ownership.
Must be >= the reconstructor’s max_ack_await_time. The compact paid-tail receipt is separate
and may outlive this record; expiring a tombstone therefore does not truncate a FIFO drain.
Default: 30 s.
fill: PixFillConfigHow the Exit keeps a funded cycle on course for its own deadline when the application is quiet.
Every other field above is a rule for stopping. This one is the single rule for going, and
it exists because the deadlines cannot be met by an idle Session: a funded cycle only recovers
once its whole emission has ridden back to the Entry, so at production dimensions a Session
averaging under ~46 return packets/s over a cycle loses its deposit to
max_recovery_time — which is every idle or light client.
See PixFillConfig for what it costs and what it is bounded by.
Trait Implementations§
Source§impl Clone for SupervisorConfig
impl Clone for SupervisorConfig
Source§fn clone(&self) -> SupervisorConfig
fn clone(&self) -> SupervisorConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for SupervisorConfig
impl Debug for SupervisorConfig
Source§impl Default for SupervisorConfig
impl Default for SupervisorConfig
Source§fn default() -> Self
fn default() -> Self
Return SupervisorConfig { ssas_per_request: 1, allow_dynamic_ssa_batches: true, max_failed_cycles: 1, max_ssa_delivery_time: Duration::from_secs(20), commitment_recommit_interval: Duration::from_secs(3), max_deposit_wait: Duration::from_secs(60), max_recovery_idle: Duration::from_secs(60), max_recovery_time: Duration::from_secs(14400), max_off_front_share_fraction: 0.25, min_share_order_sample: 16384, max_predeposit_packets: 10000, max_served_without_progress: 2048, tombstone_retention_window: Duration::from_secs(30), fill: Default::default() }
Source§impl<'de> Deserialize<'de> for SupervisorConfigwhere
SupervisorConfig: Default,
impl<'de> Deserialize<'de> for SupervisorConfigwhere
SupervisorConfig: Default,
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl PartialEq for SupervisorConfig
impl PartialEq for SupervisorConfig
Source§impl Serialize for SupervisorConfig
impl Serialize for SupervisorConfig
impl StructuralPartialEq for SupervisorConfig
Auto Trait Implementations§
impl Freeze for SupervisorConfig
impl RefUnwindSafe for SupervisorConfig
impl Send for SupervisorConfig
impl Sync for SupervisorConfig
impl Unpin for SupervisorConfig
impl UnsafeUnpin for SupervisorConfig
impl UnwindSafe for SupervisorConfig
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<'de, T> BorrowedRpcObject<'de> for Twhere
T: RpcBorrow<'de> + RpcSend,
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Conv for T
impl<T> Conv for T
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
§impl<T> FmtForward for T
impl<T> FmtForward for T
§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.§fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
§impl<T> FutureExt for T
impl<T> FutureExt for T
§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read more§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read more§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.§impl<T> Pointable for T
impl<T> Pointable for T
§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
impl<'de, T> RpcBorrow<'de> for T
impl<T> RpcObject for Twhere
T: RpcSend + RpcRecv,
impl<T> RpcRecv for T
impl<T> RpcSend for T
§impl<T> Tap for T
impl<T> Tap for T
§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read more§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read more§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read more§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read more§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read more§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read more§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.