I've noticed the strange behaviour for me if I try to implement the same function either as an inherit impl or as a trait impl.
I want to use RPIT since I'm working with futures and want to avoid dynamic dispatch.
In the code below you'll find several fn definitions that I've tried to replicate:
- with RPIT as inherit impl I'm able to successfully compile the definitions but it requires specifying all the lifetimes and I can only use a single one without rustc to complain. Because of the single lifetime I can't call this function twice in the same block
- I can fix it by using Box + dyn with 2 lifetimes. That seems to work
- If I introduce special trait and RPITIT in it I can just use with single lifetime specified and the ability to call the function twice in the same block
use std::future::Future;
use std::pin::Pin;
use std::marker::PhantomData;
#[derive(Default)]
struct Context<'a> {
data: Option<&'a PhantomData<()>>
}
#[derive(Default)]
struct EvalInherit {
expr: Option<Box<EvalInherit>>
}
impl EvalInherit {
// NOTE: none of the definions below are working and be able to call eval 2 times
//
// fn eval<'a>(&'a self, ctx: &'a mut Context<'a>) -> impl Future + 'a {
// fn eval<'a, 'b>(&'a self, ctx: &'b mut Context<'a>) -> impl Future + 'b {
// fn eval<'a>(&'a self, ctx: &'a mut Context<'a>) -> Pin<Box<impl Future + 'a>> {
// fn eval<'a, 'b>(&'a self, ctx: &'b mut Context<'a>) -> Pin<Box<impl Future + 'b>> {
//
// NOTE: the dyn definition works fine. The lifetimes should be specified explicitly
fn eval<'a, 'b>(&'a self, ctx: &'b mut Context<'a>) -> Pin<Box<dyn Future<Output = ()> + 'b>> {
Box::pin(async {
if let Some(expr) = &self.expr {
expr.eval(ctx).await;
}
})
}
}
trait Eval {
fn eval<'a>(&'a self, ctx: &mut Context<'a>) -> impl Future;
}
#[derive(Default)]
struct EvalTrait {
expr: Option<Box<EvalInherit>>
}
impl Eval for EvalTrait {
// NOTE: for trait implementation we can call eval multiple times.
// Also we specify only single lifetime
fn eval<'a>(&'a self, ctx: &mut Context<'a>) -> impl Future {
Box::pin(async {
if let Some(expr) = &self.expr {
expr.eval(ctx).await;
}
})
}
}
#[tokio::main]
async fn main() {
let mut ctx = Context::default();
let eval_inherit = EvalInherit::default();
eval_inherit.eval(&mut ctx).await;
eval_inherit.eval(&mut ctx).await;
let eval_trait = EvalTrait::default();
eval_trait.eval(&mut ctx).await;
eval_inherit.eval(&mut ctx).await;
}
I have following questions about this code:
- Is it ok that RPITIT looks much smarter and is able to elide lifetimes automatically and this works as I want to?
- Is this a bug or some kind of limitations of the raw RPIT right now?
- If this is a bug on what side? Is this RPIT is not working correctly or RPITIT allows too much flexibility for me?