I am attempting to define a struct
, in Rust, that contains a member of type async_executor::LocalExecutor
, a type that, itself, is generic over a lifetime, 'a
:
pub struct LocalExecutor<'a> {
inner: ......<Executor<'a>>,
...
...
}
My own struct must now, apparently, also be generic over a lifetime, 'a
, which means nothing to it, itself -- that lifetime is a detail of async_executor::LocalExecutor
.
#[cfg(all(test, not(target = "wasm32")))]
struct MockThing<'a> {
executor: async_executor::LocalExecutor<'a>,
}
My struct exists only when building unit-tests, where I need a mock, single-threaded executor for running async
code. Herein lies the problem: the only consumer of my struct uses #[cfg(...)]
conditional compilation, internally, to
- use my mock when compiling for unit-tests (and not WebAssembly)
- or one real implementation when compiling for WebAssembly
- or another real implementation, otherwise.
This is done with conditional compilation to ensure that the consumer, itself, is not unnecessarily generic, which would polute its public API and just push the problem of contagious generics to everything that consumes it -- a large number of things. Conditional compilation provides compile-time duck-typing, of a sort, and because that conditional compilation only exists in one place, knowledge of the implementation details is unnecessary for everyone else -- as it should be.
Neither implementation 2 nor 3 requires generic lifetimes but, because the mock one (1) must be generic over 'a
, I now have to make everything, throughout my code-base, generic over some lifetime, 'a'
! (and faff about with PhantomData
to stop the compiler from complaining that 'a
is meaningless, which it is, most of the time.)
Is there some way that I could define my mock struct in a way that does not strike this problem? It would be really convenient if I could use '_
in the member definition, like...
#[cfg(test)]
struct MockThing {
executor: async_executor::LocalExecutor<'_>,
}
... to indicate that the lifetime of executor
should be deduced from the lifetime of the MockThing
. (Of course, this does not work.)
I suppose I could also just use another async
runtime, with ambient executor, for my unit tests, and bypass the problem but that would not help me to understand what is going on, here, and, in general, how one should encapsulate lifetimes as implementation details, in structs that have members that are generic over some lifetime.
There is something I am not understanding, though: why Executor
(inside LocalExecutor
) must be generic over 'a
-- it does not contain a reference with lifetime 'a
-- and why they use PhantomData to ensure that it is generic over an invariant lifetime, 'a
, and, even, what lifetime invariance means in this case. I've been reading about it, in the nomicon and elsewhere, but it will be many days of learning before I can say I understand lifetime variance and all I want to do is "put one of those in my struct
".
Surely there must be some way to tame lifetime contagion and prevent it polluting one's entire code-base just because one type is generic over a lifetime? Help, please!
'static
in this case. As you noted, it's not really the lifetime of any reference inside the executor. It's rather the "scope" of the iterator, which allows to submit tasks with non-static lifetime to it. Since it looks like you don't want to do that, simply using'static
should solve your problem. – Subsidize'static
but only'a
on the executor. This makes sure the future lives at least as long as the executor. – Subsidize