I have the following code which fails to compie for two reasons:
fn(&Mw<M>) -> O is not 'static, even tough I think it should be.
O doesn't outlive 't which I don't know how to express in the code.
The goal is that the output O is a Future that can borrow from the world parameter, and that the TaskOnce type should be 'static which is why I can't change the trait signature to have a generic lifetime parameter.
Mutation should be 'static because it must not borrow from world
use std::future::Future;
pub trait Mutation: 'static {
type World: 'static;
fn apply(self, world: &mut Self::World);
}
pub type Mw<M> = <M as Mutation>::World;
pub trait TaskOnce: 'static {
type Mutation: Mutation;
type Output<'t>: 't + Future<Output = Self::Mutation>;
fn spawn_once<'t>(self, world: &'t Mw<Self::Mutation>) -> Self::Output<'t>;
}
impl<O: Future<Output = M>, M: Mutation> TaskOnce for fn(&Mw<M>) -> O {
type Mutation = M;
type Output<'t> = O;
fn spawn_once<'t>(self, world: &'t Mw<Self::Mutation>) -> Self::Output<'t> {
self(world)
}
}
//
// How I roughly want to use the code
//
use tokio::runtime::Runtime;
pub struct App<M: Mutation, T: TaskOnce<Mutation = M>> {
world: M::World,
task_onces: Vec<T>,
runtime: Runtime,
}
impl<M: Mutation, T: TaskOnce<Mutation = M>> App<M, T> {
fn new(world: M::World) -> Self {
Self {
world,
task_onces: Vec::new(),
runtime: Runtime::new().unwrap(),
}
}
fn run_once(&mut self, t: T) {
self.task_onces.push(t)
}
fn tick(&mut self) {
let mut mutations = Vec::new();
// tasks do all the heavy computation and they can be done in parallel
// also the order in which tasks are done doesn't matter
self.runtime.block_on(async {
while let Some(task) = self.task_onces.pop() {
let m = task.spawn_once(&self.world).await;
mutations.push(m);
}
});
// mutations are as close to a memwrite as possible
for m in mutations {
m.apply(&mut self.world)
}
}
}
I found out why this happends but I can't come up with any workarounds. Any help is appreciated.
Note that this is not the case in your current code and that's your actual problem. O needs to be introduced when 't is already set so that it can depend on it. Otherwise O will need to be the same for every lifetime 't and hence won't be able to borrow from world.
I was worried this lifetime 't would polute the signature of App but I was able to use hrtb which should work as I wanted.
use std::future::Future;
pub trait Mutation: 'static {
type World: 'static;
fn apply(self, world: &mut Self::World);
}
pub type Mw<M> = <M as Mutation>::World;
pub trait TaskOnce<'t> {
type Mutation: Mutation;
type Output: Future<Output = Self::Mutation>;
fn spawn_once(self, world: &'t Mw<Self::Mutation>) -> Self::Output;
}
impl<'t, O: 't + Future<Output = M>, M: Mutation> TaskOnce<'t> for fn(&'t Mw<M>) -> O {
type Mutation = M;
type Output = O;
fn spawn_once(self, world: &'t Mw<Self::Mutation>) -> Self::Output {
self(world)
}
}
use tokio::runtime::Runtime;
pub struct App<M: Mutation, T: for<'t> TaskOnce<'t, Mutation = M>> {
world: M::World,
task_onces: Vec<T>,
runtime: Runtime,
}
// `hrtb` here
impl<M: Mutation, T: for<'t> TaskOnce<'t, Mutation = M>> App<M, T> {
fn new(world: M::World) -> Self {
Self {
world,
task_onces: Vec::new(),
runtime: Runtime::new().unwrap(),
}
}
fn run_once(&mut self, t: T) {
self.task_onces.push(t)
}
fn tick(&mut self) {
let mut mutations = Vec::new();
// tasks do all the heavy computation and they can be done in parallel
// also the order in which tasks are done doesn't matter
self.runtime.block_on(async {
while let Some(task) = self.task_onces.pop() {
let m = task.spawn_once(&self.world).await;
mutations.push(m);
}
});
// mutations are as close to a memwrite as possible
for m in mutations {
m.apply(&mut self.world)
}
}
}