Why `fn(T) -> U` is not `'static` and help with GAT:s

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.

https://rust-lang.github.io/rfcs/1214-projections-lifetimes-and-wf.html

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)
		}
	}
}