pub trait OutSlot: 'static {
type Destination: DestinationUse;
const NAME: &'static str;
// Provided method
fn outgoing() -> Vec<OutgoingMessageMetadata> { ... }
}Expand description
A slot marker: the identity of one Out injection.
Markers decouple a handler signature from the concrete publisher type: the handler names the
slot, the include site names the policy, and the runtime pairs them. Derive it on a unit
struct with #[derive(OutSlot)] (macros feature); NAME labels the slot in
startup errors and test assertions.
§Examples
use ruststream::memory::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Order {
id: u64,
}
#[derive(Serialize, Outgoing)]
#[outgoing(name = "audit")]
struct Audited {
id: u64,
}
/// The slot the handler names; `Audit` is how startup errors and `tb.out::<Audit>()` call it.
#[derive(OutSlot)]
#[publishes(Audited)]
struct Audit;
#[subscriber("orders")]
async fn accept(order: &Order, Out(audit): Out<impl Publisher, Audit>) -> HandlerOutcome {
match audit.message(&Audited { id: order.id }).publish().await {
Ok(()) => HandlerOutcome::ack(),
Err(_) => HandlerOutcome::retry(),
}
}
fn app() -> RustStream {
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(accept).out(Audit, Publish).build();
})
}§Extending the vocabulary (broker authors)
An Out<impl X, Marker> parameter accepts any X the live value behind the slot implements,
and the core delegates its own capabilities (Publisher,
TransactionalPublisher, OwnedTransactions, RequestReply). A broker whose live value
offers more, or is not a publisher at all (a per-partition producer cache, a shard router),
declares a capability trait of its own and implements it for that value.
The body holds the arena entry, Slot, a transparent window onto the value.
Autoderef carries a method call through it but not a trait bound, so a helper written as
fn issue<L: Lanes>(lanes: &L) rejects the entry. One blanket impl over Slot that delegates
through its Deref makes helpers and bodies generic over the capability take the entry as it
is, and the concrete type still never appears in application code.
A capability like this is router-shaped: it hands out a publisher and never sends itself. A
publish through that publisher passes outside the slot view, so the test harness does not
attribute it to the slot; a test asserts it on the broker’s publish log. A capability that sets
one per-message setting and ends in one publish is no trait at all: it is a field of
Publisher::Options and a step on the publish builder.
use ruststream::memory::prelude::*;
use ruststream::memory::{ConnectedMemoryBroker, MemoryPublisher};
use ruststream::runtime::Slot;
use ruststream::PairError;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Outgoing)]
struct Event {
id: u64,
}
/// The broker's live value: a lane router, not a publisher.
pub struct LaneRouter {
publisher: MemoryPublisher,
}
/// The broker-defined capability: pick a destination lane for a shard.
pub trait ShardLanes {
fn lane(&self, shard: u64) -> (&MemoryPublisher, &'static str);
}
impl ShardLanes for LaneRouter {
fn lane(&self, shard: u64) -> (&MemoryPublisher, &'static str) {
let dest = if shard % 2 == 0 { "lanes.even" } else { "lanes.odd" };
(&self.publisher, dest)
}
}
// The one blanket impl: the entry a body holds now satisfies the bound.
impl<M, W: ShardLanes, E, Pipe, Body> ShardLanes for Slot<M, W, E, Pipe, Body> {
fn lane(&self, shard: u64) -> (&MemoryPublisher, &'static str) {
(**self).lane(shard)
}
}
/// The policy that pairs into the router.
pub struct LanePolicy;
impl PublishPolicy<ConnectedMemoryBroker> for LanePolicy {
type Live = LaneRouter;
async fn pair(self, connected: &ConnectedMemoryBroker) -> Result<LaneRouter, PairError> {
Ok(LaneRouter { publisher: Publish.pair(connected).await? })
}
}
async fn sent<L: ShardLanes + Sync>(lanes: &L, event: &Event) -> bool {
let (publisher, dest) = lanes.lane(event.id);
publisher.message(event).to(dest).publish().await.is_ok()
}
#[subscriber("events")]
async fn route(event: &Event, Out(lanes): Out<impl ShardLanes>) -> HandlerOutcome {
if sent(lanes, event).await { HandlerOutcome::ack() } else { HandlerOutcome::retry() }
}
fn app() -> RustStream {
RustStream::new(AppInfo::new("lanes", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(route).out(DefaultSlot, LanePolicy).build();
})
}Required Associated Constants§
Required Associated Types§
Sourcetype Destination: DestinationUse
type Destination: DestinationUse
The most a transform on this slot may do to a message’s destination.
Names only where every type the slot publishes leaves its destination open, because a
type that fixes its own channel is published there and the generated document says so.
#[derive(OutSlot)] folds this out of the #[publishes(..)] list through
ListOffer; a marker with no list has nothing to fold and writes Reads, which is
what the implicit DefaultSlot does - it admits every message, so it can promise
nothing. Stable Rust has no default for an associated type, so every marker writes the
line.
Provided Methods§
Sourcefn outgoing() -> Vec<OutgoingMessageMetadata>
fn outgoing() -> Vec<OutgoingMessageMetadata>
The slot’s publish dictionary as AsyncAPI metadata, one entry per
#[publishes(..)] type. The derive fills this in; the default (a marker without a
dictionary) publishes nothing declared. Called once at registration; never on the
publish path.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".