下面显示的是英文原文。
Struct
on_startup builds one value of any type; a handler takes any of its fields as a State<T> parameter.
on_startup runs once, before any broker connects, and its value becomes the application state.
Its future can await: open a pool, connect a client, read a file.
on_startup comes before with_broker, because it fixes the state type every handler is checked against.
The lifecycle hooks after it receive the same state.
#[derive(FromRef)] writes one FromRef impl per field, each cloning that field.
A handler parameter State(prices): State<Prices> resolves through that impl before the body runs.
A field that is expensive to clone goes behind an Arc, so a delivery pays one reference count.
A handler can also borrow the whole state with ctx.state() from its &mut Context<'_, (), AppState> parameter, with no clone.
A hand-written FromRef impl builds a value from several fields.
A dependency-injection container fills the same role: froodi with RustStream support, or shaku wired by hand.
Example
- Macros
- Manual
/// A price list, loaded once at startup.
#[derive(Clone)]
struct Prices {
per_item: u64,
}
/// The application state: each field is available to a handler as `State<FieldType>`.
#[derive(FromRef)]
struct AppState {
prices: Prices,
}
#[subscriber("orders", reply)]
async fn price(order: &Order, State(prices): State<Prices>) -> Receipt {
Receipt {
order_id: order.id,
total: order.items * prices.per_item,
}
}
fn app() -> impl App {
RustStream::new(AppInfo::new("orders", "0.1.0"))
.on_startup(async move |()| {
Ok::<_, Infallible>(AppState {
prices: Prices { per_item: 25 },
})
})
.with_broker(MemoryBroker::new(), |b| {
b.include(price).out_reply(Publish);
})
}
/// A price list, loaded once at startup.
#[derive(Clone)]
struct Prices {
per_item: u64,
}
/// The application state.
struct AppState {
prices: Prices,
}
// What `#[derive(FromRef)]` writes for each field: `State<Prices>` resolves through it.
impl FromRef<AppState> for Prices {
fn from_ref(state: &AppState) -> Self {
state.prices.clone()
}
}
struct Price;
// The state type is the last parameter of `Handle`; the mount checks it against the app's.
impl Handle<Order, Receipt, (), (), AppState> for Price {
fn handle(
&self,
order: &Order,
_outs: &(),
ctx: &mut Context<'_, (), AppState>,
) -> impl Future<Output = Result<Receipt, HandlerOutcome>> {
let prices = Prices::from_ref(ctx.state());
ready(Ok(Receipt {
order_id: order.id,
total: order.items * prices.per_item,
}))
}
}
fn app() -> impl App {
RustStream::new(AppInfo::new("orders", "0.1.0"))
.on_startup(async move |()| {
Ok::<_, Infallible>(AppState {
prices: Prices { per_item: 25 },
})
})
.with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Price).reply().build())
.out_reply(Publish);
})
}