Skip to main content

SsaReconstructor

Struct SsaReconstructor 

Source
pub struct SsaReconstructor<S: PixSpec> {
    commitment_builder: Cache<SsaId<S::Pseudonym>, Arc<RegisteredCommitment<S>>>,
    ssa_cycles: Cache<SsaId<S::Pseudonym>, Arc<RegisteredCycle<S>>>,
    retirement_frontiers: Mutex<HashMap<S::Pseudonym, Arc<Mutex<RetirementFrontier>>>>,
    awaiting_acks: Cache<OffchainPublicKey, Cache<HalfKeyChallenge, TaggedEncryptedPartialSsaShare<S, <S as PixSpec>::Pseudonym, PixScalar<S>>>>,
    pending_acks: Cache<SsaId<S::Pseudonym>, Arc<Mutex<DeferredAcks>>>,
    ready_resolutions: Mutex<Vec<ShareResolution<S::Pseudonym, S::AddressPrivateKey>>>,
    ready_resolutions_len: AtomicUsize,
    ready_resolution_notifier: OnceLock<Box<dyn Fn() + Send + Sync + 'static>>,
    ack_buffer_entries: Arc<AtomicUsize>,
    max_ack_buffer_entries: usize,
    ack_buffer_resync: Mutex<Option<Instant>>,
    cfg: SsaReconstructorConfig,
}
Expand description

Allows server-side reconstruction of SSAs.

There are 3 inputs that reconstructor is dependent on (in order):

  1. SSA commitments from the Client (delivered via insert_coefficient_commitments)
  2. Extraction of pending encrypted shares (added via insert_encrypted_share
  3. Decryption of pending encrypted shares via [Acknowledgement]s (via acknowledge_shares)

It is able to track SSA for multiple different pseudonyms (Sessions).

Fields§

§commitment_builder: Cache<SsaId<S::Pseudonym>, Arc<RegisteredCommitment<S>>>§ssa_cycles: Cache<SsaId<S::Pseudonym>, Arc<RegisteredCycle<S>>>

Post-commitment state of every live cycle: the part accumulator and all part builders, published and reclaimed as one unit. See SsaCycle.

§retirement_frontiers: Mutex<HashMap<S::Pseudonym, Arc<Mutex<RetirementFrontier>>>>

One compact retirement frontier per active Session pseudonym.

A plain HashMap under a short synchronous lock is deliberate. Session scopes remove their entries immediately on teardown; a cache would defer physical removal and put the same turnover-rate assumption back into the memory bound this state replaces.

§awaiting_acks: Cache<OffchainPublicKey, Cache<HalfKeyChallenge, TaggedEncryptedPartialSsaShare<S, <S as PixSpec>::Pseudonym, PixScalar<S>>>>§pending_acks: Cache<SsaId<S::Pseudonym>, Arc<Mutex<DeferredAcks>>>

Acknowledgements that arrived before their cycle’s part builders were installed, bucketed by cycle and then by polynomial.

§Why bucketed at all

The bucket key is exactly the thing whose arrival unblocks the entries inside it, so a bucket is drained once, by the installation of its own cycle, and never scanned speculatively. That is what keeps Self::acknowledge_shares free of retry work: it only ever appends to a bucket.

The original per-peer stash had to be re-scanned in full on every acknowledge_shares call, because a per-peer key says nothing about which entries have become viable. That is quadratic in the number of acks received while a cycle’s commitments are in flight, and the per-peer key aggregates across every Session sharing a first-relayer.

§Why keyed by cycle, sub-bucketed by polynomial

Part builders are installed for a whole cycle at once, so a per-polynomial key no longer buys selective draining — the drain would just walk every polynomial of the cycle. Keying by cycle makes it one lookup. The per-polynomial sub-bucket is kept because its cap is what bounds a misbehaving peer (see MAX_DEFERRED_ACKS_PER_POLYNOMIAL).

The capacity unit is cycles, not polynomials. Keyed per polynomial it was 2 * MAX_POLYS_PER_SSA entries — which one cycle can exhaust on its own, so past roughly four concurrent cycles node-wide, moka began LRU-evicting buckets and silently dropping real shares. A size eviction here is share loss, so the headroom is deliberate and the max_ack_await_time TTL is the operative bound.

§ready_resolutions: Mutex<Vec<ShareResolution<S::Pseudonym, S::AddressPrivateKey>>>

Resolutions produced by draining deferred-ack buckets at verifier-installation time, waiting for either the transport notification stream or an acknowledgement batch to collect them.

Draining happens on the commitment path (insert_coefficient_commitments), which is where the verifier that unblocks the acks is installed. That deliberately keeps the share verification off the acknowledgement hot path, but it also means the resolutions surface on a synchronous worker. The buffer bridges that worker to the transport’s asynchronous resolution stream without blocking it or making recovered deposit keys depend on later acknowledgement traffic.

§ready_resolutions_len: AtomicUsize

Length of ready_resolutions, so the common case (nothing to pick up) costs one relaxed load instead of a mutex acquisition on every ack batch.

§ready_resolution_notifier: OnceLock<Box<dyn Fn() + Send + Sync + 'static>>

Non-blocking wake-up for the asynchronous consumer of ready_resolutions.

The callback carries no data: resolutions remain owned by ready_resolutions until a consumer atomically takes them, so wake-ups may safely coalesce and channel backpressure cannot drop a recovered deposit key. OnceLock prevents two transport streams from racing as competing designated consumers; acknowledge_shares remains an opportunistic fallback.

§ack_buffer_entries: Arc<AtomicUsize>

Running estimate of the entries live in awaiting_acks, summed over every peer, so the global budget costs one relaxed load per insertion.

An estimate, and knowingly so — see Self::resync_ack_buffer for the one drift source that cannot be listened for and what keeps it from accumulating.

Behind an Arc because each peer’s inner cache decrements it from an eviction listener, and moka requires those to be 'static — they cannot borrow the reconstructor that owns them.

§max_ack_buffer_entries: usize

max_ack_buffer_bytes in entries, divided once here rather than on every insertion.

§ack_buffer_resync: Mutex<Option<Instant>>

When resync_ack_buffer last ran, and the lock that keeps two from running at once.

None until the first run, so a buffer that saturates immediately is not made to wait out an interval before its first ground-truth reading.

§cfg: SsaReconstructorConfig

Implementations§

Source§

impl<S: PixSpec + Clone> SsaReconstructor<S>

Source

fn retirement_frontier( &self, pseudonym: S::Pseudonym, ) -> Arc<Mutex<RetirementFrontier>> ⓘ

Source

fn existing_retirement_frontier( &self, pseudonym: &S::Pseudonym, ) -> Option<Arc<Mutex<RetirementFrontier>>>

Source

fn retirement_frontier_for( &self, ssa_id: &SsaId<S::Pseudonym>, ) -> Option<Arc<Mutex<RetirementFrontier>>>

Source

pub fn begin_retirement_scope( self: &Arc<Self>, pseudonym: S::Pseudonym, ) -> SsaRetirementScope<S>

Registers a Session pseudonym for compact retirement tracking.

The returned scope belongs in the same owner as that Session’s SsaCommitmentGuards. Dropping it removes the map entry immediately, while any guards, published cycles or commitment workers still in flight retain the shared frontier until their own retirement race has settled.

Source

pub fn try_new( cfg: SsaReconstructorConfig, ) -> Result<Self, PixError<S::Pseudonym>>

Creates a new SSA reconstructor from the given configuration.

Fails if the configuration does not validate. Prefer this over Self::new anywhere the configuration is assembled at runtime — a config built programmatically or read from a file is input, not a constant, and turning it into a panic makes it un-handleable by the caller.

Source

pub fn new(cfg: SsaReconstructorConfig) -> Self

Creates a new SSA reconstructor from the given configuration.

§Panics

Panics if the configuration fails validation. Use Self::try_new to handle that case instead.

Source

pub fn config(&self) -> &SsaReconstructorConfig

Returns the configuration of the reconstructor.

Source

pub fn set_ready_resolution_notifier( &self, notifier: impl Fn() + Send + Sync + 'static, ) -> bool

Installs the non-blocking wake-up used when commitment installation resolves deferred acknowledgements.

Returns false when a notifier was already installed. The callback is invoked only after the resolution-buffer mutex has been released, so it may immediately call take_ready_resolutions. If resolutions were queued before registration, registration wakes the consumer once before returning.

Source

fn notify_ready_resolutions(&self)

Source

pub fn contains_builder(&self, ssa_id: &SsaId<S::Pseudonym>) -> bool

Returns true if the reconstructor still holds a builder (SSA-part builder or commitment builder) for the given cycle. Used by tests to verify that retire_ssa cleaned up the expected state.

Source

pub fn missing_commitment_runs( &self, ssa_id: &SsaId<S::Pseudonym>, max_runs: usize, ) -> Option<Vec<(PolynomialIndex, PolynomialIndex)>>

Polynomial-index runs of ssa_id whose constant-term commitment never arrived, so the caller can ask the peer to re-send exactly those.

A cycle’s commitment ships as hundreds of packets and completes only when every one of them has landed, so without this a single lost packet is a lost Session — nothing else in the protocol can repair it, since the peer has no way to learn which piece went missing.

  • None — no registration for ssa_id. It was retired, or it went unanswered for longer than incomplete_commitment_lifetime, and either way there is nothing left for a retransmission to complete.
  • Some([]) — the commitment is complete; there is nothing to ask for. This is the only thing an empty vector means: a max_runs of zero is read as one rather than honoured.
  • Some(runs) — at most max_runs runs, lowest first. A caller that gets exactly max_runs back has not seen the whole shortfall and should ask again once these are answered.

max_runs is the asking message’s capacity, which this crate cannot know, so the caller supplies it; SsaCommitmentBuilder::missing_runs documents how the runs are chosen.

Source

fn remove_cycle(&self, ssa_id: SsaId<S::Pseudonym>)

Removes the heavyweight reconstructor state for a single SSA cycle.

A successfully recovered cycle’s compact accounting tail is owned by the Session retirement frontier, not by these caches, and intentionally survives this operation while paid SURBs drain.

Idempotent: invalidating an absent key is a no-op.

Source

fn retire_cycle( &self, ssa_id: SsaId<S::Pseudonym>, retirement: &Arc<Mutex<RetirementFrontier>>, )

Removes a cycle that has reached a terminal state and advances its Session frontier.

Retirement is recorded before the removal, so a commitment completing concurrently sees the retirement and undoes its own publication rather than resurrecting the cycle.

This is the operation for a cycle that went live and failed terminally. Successful recovery uses retire_recovered_cycle, which performs the same heavyweight teardown but installs a bounded liveness tail. It is deliberately not the operation for a commitment released before going live: that index is retried at the same value by design, and retirement would block the retry permanently. release_abandoned_commitment draws exactly that line, and escalates to this for the live case.

Source

fn retire_recovered_cycle( &self, ssa_id: SsaId<S::Pseudonym>, retirement: &Arc<Mutex<RetirementFrontier>>, tail: RecoveredSsaTail, )

Retires the heavyweight recovery state while retaining only the bounded allowance needed by SURBs that were minted before recovery and are still queued in the Exit’s FIFO.

Source

fn process_verified_ack( &self, ack: HalfKey, ack_challenge: HalfKeyChallenge, awaiting_ack_from_peer: &Cache<HalfKeyChallenge, TaggedEncryptedPartialSsaShare<S>>, ) -> Result<ProcessedAckResult<S>, PixError<S::Pseudonym>>

Source

fn defer_ack( &self, spi: SsaPolynomialId<S::Pseudonym>, deferred: (OffchainPublicKey, HalfKeyChallenge, HalfKey), )

Buckets an acknowledgement whose cycle’s part builders have not been installed yet.

O(1) — this is the entire cost the acknowledgement path pays for a deferral.

Source

fn defer_ack_into( &self, bucket: &Arc<Mutex<DeferredAcks>>, spi: SsaPolynomialId<S::Pseudonym>, deferred: (OffchainPublicKey, HalfKeyChallenge, HalfKey), )

The bucket half of defer_ack, taking the bucket rather than looking it up.

Split out so a test can hold a handle across the drain that invalidates the cache key, which is the interleaving this guards against and the one thing a single thread cannot otherwise produce — after the invalidate, get_with hands out a fresh bucket.

Source

fn drain_deferred_acks(&self, ssa_id: &SsaId<S::Pseudonym>)

Redeems the acknowledgements that were waiting for this cycle’s part builders.

Called from the commitment path immediately after the cycle is installed, so each bucket is processed exactly once and never speculatively re-scanned. Resolutions are parked in ready_resolutions for the next acknowledge_shares call, since the commitment path has no route to the upper layer.

Source

fn redeem_deferred_acks( &self, ssa_id: &SsaId<S::Pseudonym>, deferred: impl IntoIterator<Item = (OffchainPublicKey, HalfKeyChallenge, HalfKey)>, )

Processes acknowledgements whose verifier has since been installed, parking whatever they resolve to.

Shared by the two routes that can redeem a deferral — the drain on the commitment path and an orphaned append — so both produce the same resolutions in the same order.

Source

pub fn take_ready_resolutions( &self, ) -> Vec<ShareResolution<S::Pseudonym, S::AddressPrivateKey>>

Takes any resolutions parked by drain_deferred_acks.

The take is atomic with respect to producers and other consumers. This lets the transport’s notification stream and the acknowledgement fallback race safely: exactly one receives each queued resolution.

One atomic load is the entire common-case cost — the buckets are empty whenever the Entry finishes the constant-term pass before the shares that reference it arrive.

Source

fn ack_buffer_resync_interval(&self) -> Duration

Minimum wall time between two resync_ack_buffer passes.

A saturated buffer would otherwise turn every rejected insertion into an O(max_tracked_peers) scan — the overload path amplifying its own cost, which is the shape of bug this budget exists to prevent.

Derived from max_ack_await_time rather than fixed, because what a resync reclaims is entries that have aged out of that window. A constant would be wrong at both ends: too slow for a short window, so a drained buffer keeps refusing shares long after it emptied, and needlessly eager for a long one.

The resulting staleness — up to ~1.9 s at the 30 s default — only bites when the counter has drifted high and nothing is touching the caches, since redemption and any cache access drive moka’s expiry maintenance and fire the listener directly. That is the traffic-stopped case, where refusing a share costs nothing.

Source

fn resync_ack_buffer(&self)

Recomputes ack_buffer_entries from what the caches actually hold.

§Why a counter needs a backstop at all

Entries leave the buffer four ways: redeemed by their acknowledgement, expired, size-evicted from their peer’s cache, or dropped wholesale when the peer’s entry leaves awaiting_acks. The inner eviction listener catches the first three exactly. The fourth cannot be caught: dropping a moka handle does not run its eviction listener, so the outer listener falls back to invalidate_all, which is best-effort and races an insertion landing on the very cache being discarded.

Left alone, that residue only ever accumulates upward, and an over-count is far worse than an under-count: it would eventually refuse every share while the buffer sat empty, turning a memory ceiling into a permanent outage of the acknowledgement path. (The sibling HoprUnacknowledgedTicketProcessor in hopr-protocol-hopr has the same nesting and the same residue; there it only skews metrics.)

So the counter is treated as a hint that is allowed to be wrong, and ground truth is consulted at the one moment being wrong would cost something — when it says the buffer is full. try_lock rather than lock: a caller that finds a resync already running should proceed on the current estimate, not queue up behind it.

Source

fn count_ack_buffer_entries(&self) -> usize

Ground truth: the entries actually held across every peer.

Never reads ack_buffer_entries, so a test asserting the two agree is testing the counter rather than agreeing with it — the same reason deferred_ack_count recomputes from by_poly instead of reading DeferredAcks::total.

O(max_tracked_peers), and each run_pending_tasks is bounded by that cache’s pending write queue rather than its size. Both callers keep it off the steady-state path.

Source§

impl<S: PixSpec + Clone> SsaReconstructor<S>

Source

fn register_exit_commitment( &self, id: SsaId<S::Pseudonym>, params: PixParams, ) -> Result<(PixGroup<S>, Arc<RegisteredCommitment<S>>), PixError<S::Pseudonym>>

Source

fn release_abandoned_commitment( &self, ssa_id: SsaId<S::Pseudonym>, registration: &Arc<RegisteredCommitment<S>>, )

Releases a commitment that was registered but never taken over by an owner.

Deliberately not retire_ssa, which permanently moves the index behind the Session’s retirement frontier. A pre-live release instead marks the index retriable, so a replacement registration can use the same value.

Same-index retry is not a corner case: the SSA index is advanced only after every fallible step of a request has succeeded, so a request that failed keeps its index by design and the next attempt reuses it.

Escalates to a full retirement if a cycle did go live, which means the peer was asked and answered — and therefore that ownership should already have been transferred with disarm. That branch is a caller error, and retiring is the safe response to it, because a live cycle is exactly what the retirement frontier protects.

Source

pub fn new_guarded_exit_commitment( self: &Arc<Self>, id: SsaId<S::Pseudonym>, params: PixParams, ) -> Result<(PixGroup<S>, SsaCommitmentGuard<S>), PixError<S::Pseudonym>>

new_exit_commitment, with the registration owned by an SsaCommitmentGuard.

No guard is produced on failure, so a rejected duplicate never retires the registration that caused the rejection.

Trait Implementations§

Source§

impl<S: PixSpec + Clone> Default for SsaReconstructor<S>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<S: PixSpec> Drop for SsaReconstructor<S>

Source§

fn drop(&mut self)

Reports terminal resolutions that were never collected.

The transport normally drains this buffer as soon as the commitment path wakes it, with an acknowledgement batch retained as a fallback consumer. Reaching Drop with anything left therefore means every delivery route has gone away. A RecoveredSsa reported here is a deposit key the Exit held and never handed on.

Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl<S: PixSpec + Clone> ExitAcknowledgementShareProcessor<S> for SsaReconstructor<S>

Source§

fn new_exit_commitment( &self, id: SsaId<S::Pseudonym>, params: PixParams, ) -> Result<PixGroup<S>, Self::Error>

No range check on the dimensions: holding a PixParams is what proves them in range, since PixParams::try_new is the only way to make one and the two bounded fields are as wide as their types. The check this replaced tested exactly those bounds.

“The two bounded fields” is polys_per_ssa and shares_per_poly, and the omission of surplus_shares is deliberate rather than an oversight — worth stating, because the surplus does reach SsaPartBuilder::max_credited_surplus and so does size how much post-reconstruction traffic one completed polynomial may credit as liveness. try_new accepts the whole u8 range for it on purpose: the surplus is a wire value, surplus_must_not_exceed_threshold bounds only what this node will locally configure, and an extravagant surplus is caught where it costs the peer something — the Exit’s quota_range, which prices polys × (threshold + surplus) and so rises with it. A peer declaring a surplus of 255 against a threshold of 2 is asking to be charged 128× and is refused unless the operator configured a range that admits it, in which case the traffic it credits is traffic it paid for.

Source§

fn insert_encrypted_share( &self, peer: &OffchainPublicKey, challenge: HalfKeyChallenge, tagged_enc_share: TaggedEncryptedPartialSsaShare<S>, ) -> Result<(), Self::Error>

Buffers an encrypted share until its acknowledgement arrives, subject to the global byte budget.

§The bound is global, and it is not the product of the two caps

max_tracked_peers and max_awaiting_acks bound one dimension each, and their product — 2 000 × 1 000 000 by default, some 800 GB — is neither reachable nor the right thing to bound. Not reachable, because an entry exists only for a share this node has already sent, so filling it would take 66 M packets/s of egress inside the default 30 s window. Not right, because the two guard mutually exclusive concentrations: max_awaiting_acks sizes one cache per peer and has to cover every Session returning through a single first-relayer, while max_tracked_peers covers traffic spread thin. Squeezing the product would push one of them below what its own case needs, and a max_awaiting_acks set too low does not save memory — it size-evicts shares before their acknowledgements arrive.

So the real bound is max_ack_buffer_bytes, counted here across all peers at once. Validating a workload model instead would not do: a model has to assume a Session count and a packet rate, and this node enforces neither.

§Behaviour at the ceiling

The newest share is refused, rather than the oldest evicted: there is no cheap global “oldest” across per-peer caches, and the oldest is nearest its TTL anyway. Either way a full buffer means share loss — the packet is already on the wire and its acknowledgement will find nothing — which is the honest cost of a hard ceiling. PixError::AckBufferFull is deliberately not an expected error so the caller logs it.

The check and the insertion are not atomic. Concurrent inserters can overshoot the ceiling by their own number, which is the right trade: a lock on this path would cost more than the few hundred kilobytes of overshoot it would prevent.

Source§

type Error = PixError<<S as PixSpec>::Pseudonym>

Source§

fn has_pending_shares(&self, peer: &OffchainPublicKey) -> bool

Returns true if the peer has pending encrypted shares awaiting an acknowledgement. Read more
Source§

fn is_expected_error(&self, error: &Self::Error) -> bool

Returns true if the given error is an expected “not for us” skip (e.g. no acknowledgements from the peer were expected), so the caller can log it at a lower severity. Read more
Source§

fn retire_ssa(&self, ssa_id: SsaId<S::Pseudonym>)

Releases heavyweight reconstructor state for a finished or torn-down SSA cycle. Read more
Source§

fn insert_coefficient_commitments( &self, ssa_id: SsaId<S::Pseudonym>, index: CoefficientIndex, proof: Option<SsaCommitmentProof<S>>, commitments: impl Iterator<Item = (PolynomialIndex, PixGroupRepr<S>)>, ) -> Result<SsaCommitmentState<S::Pseudonym, S::DepositAddress>, Self::Error>

Adds the client commitment data. Read more
Source§

fn acknowledge_shares( &self, peer: OffchainPublicKey, acks: Vec<Acknowledgement>, ) -> Result<Vec<ShareResolution<S::Pseudonym, S::AddressPrivateKey>>, Self::Error>

Finds and acknowledges previously inserted encrypted share, using incoming [Acknowledgement]s from the upstream peer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more