Generics and Trait Boundaries
The most important generic boundary is BrokerClient.
One capability contract, several mechanisms
pub trait BrokerClient: Send {
async fn place_market_order(&mut self, ...)
-> Result<Option<i64>>;
async fn search_positions(&mut self, ...)
-> Result<Vec<BrokerPosition>>;
async fn ensure_connected(&mut self) -> Result<()> { Ok(()) }
}
Source: broker_trait.rs:128
The trait describes what execution needs. It does not require ProjectX and Rithmic to share authentication, transport, bracket behavior, reconnection, or historical-data machinery.
Static dispatch in the hot stateful path
Functions use parameters such as:
client: &mut (impl BrokerClient + Send)
This is generic static dispatch. The compiler creates code for the concrete broker type. More importantly, the caller retains a concrete owned client with its full state and the callee receives one exclusive mutable borrow.
The executor does not need Arc<Mutex<dyn BrokerClient>>, heap allocation per
call, or runtime method lookup.
Why Send appears
The executor future is spawned onto a multithreaded Tokio runtime. It may move
between worker threads whenever it is suspended. State retained across an
.await, including the concrete broker client, must therefore permit transfer
between threads.
Send does not mean two tasks may use the broker concurrently. &mut still
enforces exclusive access.
Default methods encode optional capabilities
place_entry_order defaults to a normal market order and reports that native
brackets were not used. Always-connected brokers can inherit a no-op
ensure_connected. History methods can default to empty results.
This keeps the common contract usable while allowing adapters with richer capabilities to override behavior. The risk is semantic ambiguity: a default empty history is not the same as βthe broker proved there were no fills.β Such defaults need careful callers and documentation.
The forwarding blanket implementation
impl<T: BrokerClient> BrokerClient for &mut T forwards every operation. That
makes nested mutable references produced by Option<&mut C>::as_mut() continue
to satisfy generic helpers without manual dereferencing at each call site.
Source: broker_trait.rs:295
This is a small but powerful generic pattern: implement a capability for a borrow of every type that already has the capability.
Traits versus enums
Aikido uses both deliberately:
- an open set of broker implementations is modeled with a trait;
- a closed set of executor commands is modeled with an enum;
- strategy implementations may use trait objects where heterogeneous values must coexist in one collection;
- small, fixed runtime choices such as broker order status use enums.
Use a trait when downstream implementations should be extensible. Use an enum when the protocol variants should remain centrally known and exhaustively handled.