It lets you enable different methods depending on what your type is parameterised by.
Some examples would be when you need to execute operations in a certain order and want to ensure this at compile time instead of runtime. This is an instance of the Typestate pattern.
You might also want to give a method a similar name, but switch between implementations. Like in actix_broker::Broker
where you can use the type parameter to specify whether a message should be broadcast across the entire system, or just for the current thread.
You can see something similar in uom::si::Quantity
where the base Quantity
type is parameterised by the dimension (e.g. m/s²
has a "1" for the length dimension and "-2" for the time dimension). There are lots of different combinations of quantities and conversion traits, so they decided to generate copies of things like ceil()
and floor()
. Given the way uom
is designed, you would need higher-kinded types to express all this in a single generic method, so "overloading" with multiple impl blocks is an alternative.
Another occasion is where you may want to relax restrictions on what your type, T
, needs to satisfy for certain methods. Have a look at this component that broadcasts the same message to multiple listeners and collects their responses.
// declaration and constructors.
pub struct Broadcast<M>
where
M: Message + Send + 'static,
{
listeners: Vec<Recipient<M>>,
}
impl<M> Broadcast<M>
where
M: Message + Send + 'static,
{
pub fn new() -> Self { Broadcast::with_listeners(Vec::new()) }
}
// Broadcasting a normal message by sending a copy to each listener.
impl<M> Broadcast<M>
where
M: Message + Clone + Send + 'static,
{
pub fn broadcast(
&self,
msg: M,
) -> impl Future<Output = Result<Vec<M::Result>, MailboxError>> {
let pending: Vec<_> = self
.listeners
.read()
.unwrap()
.iter()
.map(|l| l.send(msg.clone()))
.collect();
futures::future::try_join_all(pending)
}
}
impl<M, E> Broadcast<M>
where
M: Message<Result = Result<(), E>> + Clone + Send + 'static,
M::Result: Send + 'static,
E: Into<Error> + Send,
{
/// A form of [`Broadcast::broadcast()`] which will flatten the result
/// and short-circuit on failure, cancelling any messages which are still
/// en-route.
pub fn broadcast_fallible(
&self,
msg: M,
) -> impl Future<Output = Result<(), Error>> {
let pending: Vec<_> = self
.listeners
.read()
.unwrap()
.iter()
.map(|l| l.send(msg.clone()).map_result_into())
.collect();
futures::future::try_join_all(pending).map_ok(|_| ())
}
}
I've created a specialised Broadcast::broadcast_fallible()
so instead of getting a future which completes with Vec<Result<(), Error>>
when all listeners respond, I get a single Result<(), Error>
which will short-circuit and cancel any pending messages the moment anything responds with an error.