I'm trying to write a generic trait for sending messages between processes. The simplified version of it looks like this:
trait Sender<T> {
fn send_msg(&mut self, to: u64, body: T) -> Result<()>;
}
I want to be able to send messages that contain references, so as to avoid copying data:
#[derive(Serialize)]
enum MsgBody<'a> {
Success,
Fail(Error),
Write { offset: u64, buf: &'a [u8] },
}
However I've run into an issue where the type parameter T
can only refer to a single lifetime, preventing references of any lifetime from being used. To illustrate this consider this dummy implementation of Sender<T>
:
struct SenderImpl<T>(PhantomData<T>);
impl<T: Serialize> Sender<T> for SenderImpl<T> {
fn send_msg(&mut self, to: u64, body: T) -> Result<()> {
Ok(())
}
}
Now if I try to return a sender that will work for any lifetime parameter
fn ref_sender() -> impl for<'a> Sender<MsgBody<'a>> {
SenderImpl(PhantomData)
}
then I get a compilation error because the type parameter T
is not generic over all possible lifetimes:
error: implementation of `Sender` is not general enough
--> src/lib.rs:29:5
|
29 | SenderImpl(PhantomData)
| ^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Sender` is not general enough
|
= note: `SenderImpl<MsgBody<'2>>` must implement `Sender<MsgBody<'1>>`, for any lifetime `'1`...
= note: ...but it actually implements `Sender<MsgBody<'2>>`, for some specific lifetime `'2`
The error makes sense but I'm not sure how to express what I want. What I'd like to say is this
fn ref_sender() -> impl for<'a> Sender<MsgBody<'a>> {
SenderImpl(PhantomData::<for<'a> MsgBody<'a>>)
}
but of course for<'a> MsgBody<'a>
is not actually a type.
I can work-around this by using MsgBody
in the Sender
trait rather than making it generic (you can see that here). I don't like this solution though as it sacrifices flexibility.
Is there a way to get the generic Sender<T>
to work with types that have a lifetime parameter?
Edit: Here's the code used in this question: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0aac9134639e9593698bfe9da7722d9f
CodePudding user response:
Instead of making the trait generic, move the generic type parameter to send_msg
:
trait Sender {
fn send_msg<T: Serialize>(&mut self, to: u64, body: T) -> Result<()>;
}
Then every call to send_msg
can use a different lifetime parameter. The full solution is here.
CodePudding user response:
As you've noted, &T
needs a lifetime to be a complete type and for<'a> &'a T
doesn't exist. The concrete problem is that SenderImpl<T>
must have a complete type T
and only implements Sender
with that type.
You can workaround this by avoiding references in SenderImpl<T>
(by making T = [u8]
) and allow it to implement Sender<&T>
for any lifetime:
struct SenderImpl<T: ?Sized>(PhantomData<T>);
impl<'a, T: Serialize ?Sized> Sender<&'a T> for SenderImpl<T> {
fn send_msg(&mut self, to: u64, body: &'a T) -> Result<()> {
Ok(())
}
}
Edit:
This approach doesn't seem to work in the case where T has it's own lifetime parameter.
You could also make it generic over any specific type with lifetimes, just SenderImpl
won't have a specific type associated with it beyond what the trait implementation implies:
struct SenderImpl;
impl<'a> Sender<MsgBody<'a>> for SenderImpl {
fn send_msg(&mut self, to: u64, body: MsgBody<'a>) -> Result<()> {
Ok(())
}
}
fn ref_sender() -> impl for<'a> Sender<MsgBody<'a>> {
SenderImpl
}
Or, similarly to the self-answer, you can implement Sender<T>
for any type. But like the above, SenderImpl
obviously won't be specific to any in particular:
struct SenderImpl;
impl<T: Serialize> Sender<T> for SenderImpl {
fn send_msg(&mut self, to: u64, body: T) -> Result<()> {
Ok(())
}
}
fn ref_sender() -> impl for<'a> Sender<MsgBody<'a>> {
SenderImpl
}