Skip to main content

ProxyControl

Struct ProxyControl 

Source
pub struct ProxyControl { /* private fields */ }
Expand description

A handle onto a running proxy.

Cheap to clone — one Arc bump — and every clone reads and writes the same state, so a handle may be moved into a task, kept beside the proxy, or both. It holds no lock across an await and takes no part in the forwarding path.

Obtained from TransparentProxy::control, which can be called at any point after the proxy is constructed — including before TransparentProxy::run is awaited, which is the usual case, because run() does not return until the proxy is finished.

§What it reports

The first two are snapshots of a proxy that keeps moving. A session named by sessions() may end before the next line of the caller’s code runs; that is not a defect in the snapshot but the reason every request that takes a SessionId can answer ControlError::SessionEnded.

§What it changes, and how far each change reaches

Implementations§

Source§

impl ProxyControl

Source

pub fn local_addr(&self) -> Result<SocketAddr, ProxyError>

The address this proxy’s listener is bound to.

ProxyError::NotBound until TransparentProxy::run has bound the endpoint, and again once it has returned and the endpoint is gone. Between those two points this is the address a client connects to, which is the reason the method exists: a proxy configured with port 0 chooses its port inside run(), and before this there was no way to learn which port that was without binding the socket in the caller and handing it in.

A bound listener can still fail to report its address — the query goes to the operating system — and that failure is reported as ProxyError::Listener, distinct from not being bound at all.

Source

pub fn sessions(&self) -> Vec<SessionId>

The ids of the sessions that are live right now.

An id appears here when its session starts running and disappears when that session ends, for any reason at all — including the ones no teardown site could be written for. It is not a log: a session that has ended leaves nothing behind, so a caller that wants a history of everything the proxy handled reads ProxyEvent::SessionStarted and ProxyEvent::SessionEnded from its observer instead.

Sorted ascending, which makes the value comparable between two calls without the caller sorting it first. Ids are minted monotonically per proxy, so the order is also arrival order.

§This list and the event stream do not have to agree

SessionStarted is emitted when a connection is accepted, before the session has dialled the upstream relay; registration here happens when the session begins to run. A session whose upstream connect fails is therefore reported as started, is briefly listed here, and then disappears — and a session that has been accepted but has not been polled yet is in the event stream and not in this list. Sessions driven by constructing a ProxySession directly, rather than through a proxy’s accept loop, appear in neither: they belong to no proxy and so to no control plane.

Source

pub fn stats(&self) -> ProxyStats

What this proxy’s shaping has done, across every session it has accepted — including the ones that have already ended.

The complement of Self::sessions, and the difference between them is the whole reason this exists. That list is what is live now and shrinks when a client disconnects; these figures are cumulative and monotone, so a caller polling them across a disconnect sees them hold rather than fall. A total summed over the live list could not have that property: session registrations are released by a Drop, and a statistic that goes down under normal operation cannot be alerted on.

It follows that a session which ended for any of the reasons no teardown site could be written for — its whole future dropped, its task cancelled mid-write — has still contributed everything it moved, because each unit was charged at the instant it was handled.

Defined before the proxy has bound: a proxy that has accepted nothing answers ProxyStats::default() rather than an error, the same shape Self::sessions takes.

§An all-zero answer means “no profile”, not “no traffic”

Every counter behind this is written by the shaping path, which a session with no ShapeProfile never enters. A proxy forwarding gigabytes with no profile configured reports ProxyStats::default(), and nothing in the value distinguishes that from a proxy nothing has connected to. Ask Self::sessions, or an observer’s event stream, which of the two it is.

§Sessions driven directly are not counted

A ProxySession constructed by a caller instead of accepted by this proxy belongs to no control plane, so it reports only its own ShapeStats — the same rule Self::sessions follows, and for the same reason: a proxy must not claim traffic it never accepted.

§Cost

A read of about forty relaxed atomics plus nine per class row, and one Vec and one String allocated per class row. Cheap, and still a reader’s call rather than something to poll in a tight loop.

Source

pub fn reset_stats(&self)

Zero every figure Self::stats reports, and start again from here.

For a caller measuring a phase rather than a run: reset, drive the traffic, read. Without it the only way to get a phase figure is to subtract two snapshots, which is correct but leaves every assertion written against a difference rather than a value.

§What it does not do

It does not touch any session’s own ShapeStats. Those are a session’s for its whole life, and a proxy-level verb that silently rewrote them would make a scenario reading both see two different pasts.

It does not clear the class rows themselves, only their counters. The rows are sized once, from the first shaped session this proxy accepts, because a class index means what the scheduler that produced it says it means; dropping them here would let the next session install a different class list and report figures under it.

§It does not stop traffic

The counters are cleared one relaxed store at a time while the forwarding tasks keep charging them, so a reset that races a live stream can land between two increments of the same unit and leave a row part-cleared. There is no atomic form of this — no primitive clears forty counters at once — and pausing the proxy to get one would be a far larger promise than the figures are worth. Reset when the traffic you are about to measure has not started.

Source

pub fn set_transport( &self, leg: Leg, p: TransportProfile, ) -> Result<(), ControlError>

Set the QUIC transport parameters one leg uses — for connections it has not made yet.

§This never reaches a connection that already exists

Read that sentence as the whole of what this method does, because every other sentence here is a consequence of it. A QUIC connection takes its transport configuration once, at setup, and holds it for life; quinn exposes four setters on a connection that is already up — the two stream-count limits and the two windows — and no way at all to hand it a different configuration. So a profile set here reaches:

  • on Leg::Upstream, the next connection this proxy opens, which is the next session it accepts, because a session dials the relay once and then forwards over that connection until it ends;

  • on Leg::Client, the next connection this proxy accepts, which the proxy does not initiate and which may never arrive.

It follows that on a proxy which never dials or accepts again — one whose clients have all connected, one nothing is connecting to, one about to be cancelled — this call is a permanent silent no-op that returns Ok(()). Nothing about the return value distinguishes that from a setting that reached every connection made afterwards, because there is nothing to distinguish: the parameters were installed, and what did or did not happen next is traffic rather than configuration. A caller that needs the change to bite on a session already running has one instrument, and it is ProxyControl::close_session — a leg that reconnects is a leg that installs this.

§Every call is absolute: None is a default, not “leave alone”

The profile is built into a fresh quinn::TransportConfig — a TransportConfig::default() with the profile applied over it — and that config is installed whole. So a field left None is not carried over from the profile installed before it: it takes quinn’s default.

Two consecutive calls therefore do not compose. A call setting stream_receive_window followed by a call setting only max_idle_timeout leaves the leg with the idle timeout and quinn’s default window, not with both settings. TransportProfile::default() puts a leg back on quinn’s defaults entirely, which is the only way to clear a setting and is why this is worth stating: a merge would look like a convenience and would make None mean two different things — “unset” when a profile is built and “unchanged” when it is installed — leaving no profile that could ever clear a field, and no way for a caller restoring normal conditions to say so.

§What it replaces

Whatever that leg’s transport parameters were, whether they came from a TransportProfile or from a raw quinn::TransportConfig in the configuration the proxy was built with. The two cannot be combined — see ProxyError::TransportConfigAndProfile — so a live profile that was merged with a configured raw config would be a merge that cannot be written; a live profile that sat beside one would turn every subsequent connect into that refusal. Replacing is the only answer that leaves the leg usable, and it means a caller who set a raw config at build time loses it from the first call to this.

The profile is built through the leg’s own TransportInstaller when the proxy was given one, so a leg keeps building its configuration the way it always did.

§Errors

ControlError::Profile for a profile the leg would not have started with — the same check, reporting the same error, run here rather than at the next connect, so a refusal is attached to the call that caused it instead of to a connection failure minutes later.

ControlError::Unsupported on Leg::Upstream when the upstream is WebTransport. That endpoint is built inside the WebTransport library, which takes no quinn::TransportConfig and hands back no endpoint to install one on, so the profile could only be stored and ignored.

Source

pub fn set_shape(&self, p: ShapeProfile) -> Result<(), ControlError>

Replace the shaping profile this proxy’s sessions pace with.

§What it reaches, and when

Every session this proxy accepts afterwards shapes with p, whatever the profile it was configured with.

A session that is already running picks p up at the next stream it forwards, not at the next object. That granularity is forced: a stream’s egress queue holds the scheduler its units were admitted under, and a class is an index into that scheduler’s class list, so a unit classified under one profile and released under another charges one profile’s class against the other profile’s buckets. Streams already forwarding therefore run to their end under the profile they started with, and on MoQT that is rarely a long wait — media arrives on a fresh unidirectional stream per subgroup.

A session that started unshaped stays unshaped for its whole life, and nothing here changes that. Shaping classifies ObjectMeta, which only the object framer produces, and whether a session frames at all is settled at session start from the profile it was configured with. A session that began as a byte pump has no objects to classify and no queue to pace.

§A session whose class list would change keeps its own profile

A running session takes p only if p’s class names are the same, in the same order, as the ones the session started with. Otherwise it keeps the profile it has until it ends, and only sessions accepted afterwards get the new one.

The reason is the statistics. ShapeStats has one row per configured class, pre-sized when the session is constructed and never resized, and a class is charged to its row by position. A profile with a different class list would therefore charge its classes to rows still named after the old profile’s — every number in ProxySession::shape_stats correct, and every label on it wrong — or, for a class beyond the end of the original list, silently to the default row. Refusing the swap per session is the only answer that keeps a reader’s numbers attributable. To move a running session onto a different class list, end it with ProxyControl::close_session; its replacement starts on the new profile.

§Buckets start full

A session that takes p builds a scheduler for it, and a fresh scheduler’s buckets are full as of that instant — the same choice a session’s first scheduler makes, so that a session does not open with a burst-sized delay in front of its first object. Setting the same profile repeatedly therefore hands every class a fresh burst each time, and a caller doing that in a tight loop measures no rate limit at all. The report-once diagnostics (ImpairmentKind::ShapeRuleUnmatchable, ImpairmentKind::ShapeBurstBelowUnit) start again with the new scheduler too, so a rule that is unmatchable under both profiles is reported once per profile rather than once per session.

§Errors

ControlError::Shape for a profile ShapeProfile::try_new rejects. Since that constructor is the only way to build a ShapeProfile, a profile arriving here has already passed it and the refusal is unreachable today; the check is re-run rather than assumed so that the rules live in exactly one place and a later construction path cannot arrive here unchecked.

Source

pub fn set_shaper_enabled(&self, on: bool)

Turn pacing off, or back on, for every session this proxy is running and every session it accepts afterwards.

Off is not “no profile”. The profile stays exactly where it was, the classes keep claiming units, the per-stream queue depth keeps applying and the statistics keep moving — what stops is the token bucket and the discipline, so every unit is released as soon as the queue reaches it. set_shaper_enabled(true) resumes with the same configuration and the same counters, which is what makes this usable as a switch in a timeline rather than as a way of throwing a profile away.

§When a stream that is already held resumes

The switch is read on the next release decision a queue makes, and a queue makes one when its head’s wait expires. Switching pacing off therefore does not reach into a wait that is already running; it changes the answer the stream gets when that wait ends. How long that is depends on why the stream was held:

  • held by a bucket that will refill — one unit’s worth of the configured rate, so a stream paced at a real rate resumes within one pacing interval;

  • held behind another class — as soon as that class drains, which is now immediate;

  • held by a bucket configured at zero, or one whose burst cannot cover a unit — there is no refill instant, so the stream’s wait is the queue’s max_hold clamp and the switch is not read until it fires. On a stopped class, switching pacing off does not promptly release what is already queued. Nothing in this crate can: the wait is a timer a per-stream queue armed, and there is no session-wide wake that reaches one. Use ProxyControl::set_shape to move the session onto a profile with a rate, or end the session, if that is what is wanted.

§It changes nothing on a proxy with no profile

Returns nothing, and cannot fail, so it is silent about a proxy where no session has a ShapeProfile to pace with — there is nothing to switch and no consequence to report. That is the one case where calling this has no observable effect whatever.

Source

pub fn close_session( &self, id: SessionId, code: u32, reason: &[u8], ) -> Result<(), ControlError>

End one session, giving its egress queues a bounded window to flush first, and close both of its legs with code and reason.

Returns as soon as the request has been taken. The drain and the close happen in the session, which is the only place that can see them through — a method that waited would have to hold the caller for as long as the drain took, and the drain is bounded precisely so that nobody has to.

§The window, and what happens at the end of it

The window is EgressConfig::drain_timeout from the session’s configuration, 100 ms by default. Within it, the session keeps running exactly as it was: units come off the per-stream queues at their release times, a shaped class keeps paying its bucket, and a hook that deferred something still gets it written. The window ends early — and this is the common case — the moment the session has nothing queued anywhere.

When it ends because the timeout expired, whatever is still queued is abandoned and reported, once per stream, as ImpairmentKind::QueuedBytesAtTeardown. It is not flushed first. A flush at that point would hand the bytes to a connection that is about to send CONNECTION_CLOSE, which discards whatever it had buffered — so the bytes would be neither confirmably delivered nor confirmably lost, and no count of them would add up. Abandoning them keeps the arithmetic exact: what the peer received plus what the impairments name is what was queued when this was called.

Either way the session then closes with code and reason. That part is unconditional for the call that was accepted — a drain that ran out of time changes what reached the peer, never what the close says. A call that is refused starts nothing at all; see Errors, which covers the one refusal a caller can produce deliberately.

§Control streams are not repaired

Nothing is synthesized on a control stream to tidy up the close. If a control message was half-written when the window closed, the peer gets a truncated message and the session reports ImpairmentKind::ControlStreamTruncated. Completing the message would mean the proxy inventing control-stream bytes that neither peer wrote.

§Errors

ControlError::SessionEnded when the session has already ended or is already ending — the ordinary race, since a session may end between listing it and closing it — and ControlError::NoSuchSession for an id this proxy never ran.

“Already ending” covers one case that is not a race at all, and a caller can produce it deliberately: a second call while the first call’s drain window is still open. The first close fixed the code and the reason and nothing revises them, so a second call’s pair would reach neither peer. It is refused here rather than accepted, because the only alternative is to take a request and drop it — the session’s command task is inside the first drain and cancels when it comes out, so it never returns for a second. A caller that wants a different code has to ask before the first close, not after it.

One thing can survive a refusal, and only in the direction that helps: a call that got as far as fixing the pair and then found the session unreachable still closes it with what was asked for. So a caller that sees this error was either late for everything — the two refusals above — or late only for the drain.

§What an observer sees

This is the one verb here that produces events, and it produces them through the session rather than from this call — after the drain, which is what makes them a record of what happened rather than of what was asked for:

  • one ProxyEvent::SessionEnded whose reason begins *control plane closed the session* and quotes code and reason. That wording is the point: a hook’s Action::CloseSession reaches the same latch and reports *hook closed the session*, and for a while both said the latter, so an operator ending a session was recorded as the scenario under test ending it.
  • one ImpairmentKind::QueuedBytesAtTeardown per stream the window ran out on, and none at all for the ordinary case where everything drained.
  • at most one ImpairmentKind::ControlStreamTruncated per control direction that was mid-message when the window closed.

Nothing is emitted at the moment the request is accepted. The return value is that answer, and an event that duplicated it would be the only event in this enum an observer could receive for something that had not happened yet.

Source

pub fn reset_stream( &self, id: SessionId, stream: StreamKey, code: u64, ) -> Result<(), ControlError>

Reset one live forwarded stream, immediately.

stream names a stream this session is forwarding; the key is the one carried on the events and hook contexts for that stream. code becomes the RESET_STREAM application error code on the destination, and the source is stopped with the same code, so both peers learn the stream was abandoned rather than finished.

§Bytes already handed to the transport are gone

A reset abandons the destination stream. Whatever the proxy had written but the transport had not yet acknowledged goes with it — QUIC does not retransmit data on a stream that has been reset — and so does everything this stream still had queued. That is what a reset is, and it is why the method exists, but it means the peer’s view of this stream ends at an arbitrary byte and no count of what it received is predictable from what was sent.

§Errors

ControlError::NoSuchStream when no stream with that key is live — which is the same answer for a key that never existed and for one whose stream has already ended, because a stream’s registration is removed as it ends and nothing is left to tell them apart. Plus the two session-level refusals.

§One case where Ok(()) is delivery rather than a reset

The request is handed to the task that owns the stream’s write half and is acted on the next time that task comes round its own loop, which is immediately in every case but one. A control direction on the forward-first pipe stops reading its request channel once it is holding COMMAND_QUEUE_DEPTH injections it has not been able to place — the backpressure that makes a further ProxyControl::inject_control answer ControlError::SessionEnded rather than queue without limit — and a reset handed over in that state waits until an injection can be written, which that method’s own documentation says can be never. Below that depth, and on every data stream, the reset is served at once.

§No observer event, and neither existing one may be borrowed

The stream’s task performs the reset and emits nothing. ProxyEvent::StreamReset means a teardown this proxy observed a peer perform, and ProxyEvent::ActionApplied means a hook asked for one at a named site. Reusing either would make it ambiguous for every reader that already relies on it — an observer counting peer resets would start counting the operator’s, with nothing in the payload to separate them — and this crate does not widen the meaning of a shipped event to save adding one.

A variant of its own was considered and declined: it would carry nothing the caller does not already hold. The session, the stream and the code came from this call, and the answer to “did it happen” is the return value. What is worth observing is the consequence, and that is observed where consequences are — at the peer, as a RESET_STREAM carrying code, which is what this crate’s own gate for the method checks.

Anything the stream still had queued goes with it and is not reported as ImpairmentKind::QueuedBytesAtTeardown. That report is about a teardown losing bytes it was trying to deliver; here the caller asked for the destination to be abandoned, and the bytes going with it is what the word means rather than a reduction in what the proxy could do.

Source

pub fn inject_control( &self, id: SessionId, leg: Leg, bytes: Vec<u8>, ) -> Result<(), ControlError>

Put a control message on one leg of a live session, in sequence.

leg names whose decoder the message is for: Leg::Client writes toward the client, Leg::Upstream toward the relay. The bytes are written onto the session’s existing control stream, interleaved with the messages already flowing on it, at a point the receiving decoder accepts.

§What “in sequence” costs, and why it is not a fresh stream

A new stream would be far simpler and would look like working code: the write succeeds, the bytes arrive. The peer reads them as whatever a new stream of that kind is — a data stream header on a fresh unidirectional stream, a request on a fresh bidirectional one — and the message is never seen as a control message at all. On the drafts that carry the control plane on unidirectional streams it would be worse than useless: a second stream announcing itself as a control stream is a second SETUP, which a peer is entitled to close the session over. So the write goes to the task that owns the control stream’s write half, and lands between two forwarded messages rather than inside one — a control stream is a single framed byte sequence, and bytes spliced into the middle of a message’s payload desynchronize the peer’s decoder for the rest of the session.

Because of that, a message injected while one is mid-flight is held until the message in flight has been written. That wait is bounded by the peer finishing the write it started, not by anything this proxy does.

§A held message can wait forever, and this returns Ok(()) anyway

The call returns as soon as the request has been taken, before any write is attempted — it has to, or a caller would be held for as long as the peer took. So Ok(()) means accepted for writing, and on the forward-first control pipe — the one a session takes unless a hook declared Interest::CONTROL — there are two ways for the write never to happen:

  • the direction goes quiet mid-message. That pipe forwards read chunks and finds the boundaries in the bytes it is forwarding, so a direction whose peer stopped writing part-way through a message never reaches one. The injection waits for a peer that may never write again.
  • the proxy has lost the framing. The boundary walk is driven by the session’s draft, which for the moq-00 cohort (drafts 07-14) is a configured guess until a SETUP is peeked. A wrong guess makes the lengths wrong; rather than place a message by guesswork the walk latches to the boundaries are unknown, and from then on nothing is injected on that direction at all. That is deliberate and it is the better of the two outcomes — a misplaced injection desynchronizes the peer’s decoder for the rest of the session, while one that never arrives leaves every other message intact.

A message still held when the session ends is discarded, and nothing reports it: no event, no impairment, no error. A caller that needs to know an injection landed observes it at the peer, which is what this crate’s own gate for the method does.

That silence is the weakest point on this page, and it is written down rather than smoothed over. An accepted request that never reaches the wire is exactly the failure this crate refuses everywhere else, and the only thing standing in for a report is the paragraph above. An impairment for it was considered and not added: the held messages live in a local deque of the control pipe, the pipe returns from seven places, and a report wired into some of them would be a promise that is kept for some teardowns and not others — which is worse than no promise, because a scenario would then read the absence of the event as delivery. The report that would be worth having has to be raised by something whose completeness is structural, the way SessionGuard is for the session census, and that is not a wiring change.

A hook declaring Interest::CONTROL puts the stream on the pipe that decodes each message before forwarding it, where every return to the select is a boundary by construction and neither case above exists.

§bytes must already be framed, and is not checked

Pass a complete, framed control message for the session’s draft — what AnyControlMessage::encode produces, which is the type varint, the length field, and the payload. The proxy writes it verbatim: it does not frame it, does not decode it, and does not know what it says. A payload passed without its framing is the second way to get this wrong that still looks like working code — the write succeeds and the peer’s decoder reads the first bytes of the payload as a message type and length, and is lost from there on. Nothing is validated because there is nothing to answer with: the refusals this method can give are about reaching a session, and inventing a those bytes were not a message refusal would mean decoding every injection on the session’s forwarding path.

§Which stream that is, per draft

Two topologies, and the session picks between them from its draft. Through draft 16 the control plane is one client-initiated bidirectional stream and leg names which of its two directions to write on. From draft 17 it is a pair of unidirectional streams, one opened by each peer, and bidirectional streams carry requests instead — draft-17 Section 3.3. There leg names which of the two unidirectional control streams to write on, and a request stream is never a candidate: the session routes an injection to the control leg’s channel, which only a control direction’s pipe ever serves.

The leg mapping is the same on both: the leg is the peer whose decoder reads what is written. Leg::Upstream puts the message in front of the relay, Leg::Client in front of the client.

§Errors

The two session-level refusals. A session whose control stream has not been established yet is not an error: the message waits and is written as soon as there is a stream to write it on. On drafts 17 and later that wait covers a little more ground — the control stream is identified from the first varint of a unidirectional stream, so a session whose peer has not opened one yet has no control stream in that direction and the message waits for it exactly as it waits for a message boundary.

§What an observer sees

Nothing for the injection itself — no event on acceptance, none on the write, none on the discard described above. The bytes are written verbatim and are never decoded here, so there is no message to put on a ProxyEvent::ControlMessage and no honest way to synthesize one; and the peer’s own reaction, if it has one, arrives as ordinary forwarded traffic.

Trait Implementations§

Source§

impl Clone for ProxyControl

Source§

fn clone(&self) -> ProxyControl

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ProxyControl

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. 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
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

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

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

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

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

Source§

type Error = !

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.
Source§

impl<T> WithSubscriber for T

Source§

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

fn with_current_subscriber(self) -> WithDispatch<Self>

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