use futures::Future;
use std::pin::Pin;
pub struct MyBackend<'a> {
_marker: std::marker::PhantomData<&'a ()>,
}
impl<'a> Backend<'a> for MyBackend<'a> {
type MyStorage = AtomicStorage;
}
pub trait Storage<'a> {}
pub struct AtomicStorage {}
impl<'a> Storage<'a> for AtomicStorage {}
pub trait Backend<'a>: Send {
type MyStorage: Storage<'a>;
fn store<F>(&mut self, _update: F) -> Pin<Box<dyn Future<Output = ()> + Send>>
where
F: Fn(Self::MyStorage),
{
unimplemented!()
}
}
// https://stackoverflow.com/questions/50547766/how-can-i-get-impl-trait-to-use-the-appropriate-lifetime-for-a-mutable-reference
pub trait Captures<'a> {}
impl<'a, T: ?Sized> Captures<'a> for T {}
#[cfg(feature = "use_async_fn")]
pub async fn transfer<'a, B: 'a + Backend<'a> + Send + Sync>(backend: &mut B) {
backend.store(|_t| {}).await
}
#[cfg(not(feature = "use_async_fn"))]
pub fn transfer<'a, 'b, B: 'a + Backend<'a> + Send + Sync>(
backend: &'b mut B,
) -> impl 'b + Captures<'a> + std::future::Future<Output = ()> + Send {
async move { backend.store(|_t| {}).await }
}
// This works
fn _test1<'a>(mut backend: impl 'a + Backend<'a> + Send + Sync) {
let _: Pin<Box<dyn Send>> = Box::pin(async {
transfer(&mut backend).await;
});
}
// This doesn't
fn _test2<'a>(mut backend: MyBackend<'a>) {
let _: Pin<Box<dyn Send>> = Box::pin(async {
transfer(&mut backend).await;
});
}
fn _hidemytype<'a>(backend: MyBackend<'a>) -> impl 'a + Backend<'a> + Send + Sync {
backend
}
// This does!
fn _test3<'a>(backend: MyBackend<'a>) {
let _: Pin<Box<dyn Send>> = Box::pin(async {
transfer(&mut _hidemytype(backend)).await;
});
}
// And so does this
fn _test4<'a>(backend: MyBackend<'a>) {
_test1(backend);
}
fn main() {}
It succeeds with cargo build (using the second transfer), but if you use the first transfer by running cargo build --features use_async_fn, you get a very confusing error:
$ cargo build --features use_async_fn
Compiling future_pin_issue v0.1.0 (/home/joe/issue-test)
error: implementation of `Backend` is not general enough
--> src/main.rs:52:33
|
52 | let _: Pin<Box<dyn Send>> = Box::pin(async {
| _________________________________^
53 | | transfer(&mut backend).await;
54 | | });
| |______^ implementation of `Backend` is not general enough
|
= note: `Backend<'1>` would have to be implemented for the type `MyBackend<'0>`, for any two lifetimes `'0` and `'1`...
= note: ...but `Backend<'2>` is actually implemented for the type `MyBackend<'2>`, for some specific lifetime `'2`
error: could not compile `future_pin_issue` due to previous error
The second transfer implementation (used when use_async_fn is off) is a workaround based on the desugar of async fn, plus a Captures trait I found on stackoverflow.
Confusingly, the error only ever occurs in _test2. _test3 is especially concerning, because it means that "forgetting" information about the type makes the typechecking succeed somehow!
Removing the Backend::<'a>::MyStorage associated type, changing store to store<F,MyStorage: Storage<'a>>, and calling backend.store::<_,AtomicStorage>(...) rather than backend.store(...) also fixes the error.
This looks like a potential type inference bug, but it might be expected behavior. If it's expected, I think the error messages could use some work.
With the following files:
Cargo.toml
:src/main.rs
:It succeeds with
cargo build
(using the secondtransfer
), but if you use the firsttransfer
by runningcargo build --features use_async_fn
, you get a very confusing error:The second
transfer
implementation (used whenuse_async_fn
is off) is a workaround based on the desugar ofasync fn
, plus aCaptures
trait I found on stackoverflow.Confusingly, the error only ever occurs in
_test2
._test3
is especially concerning, because it means that "forgetting" information about the type makes the typechecking succeed somehow!Removing the
Backend::<'a>::MyStorage
associated type, changingstore
tostore<F,MyStorage: Storage<'a>>
, and callingbackend.store::<_,AtomicStorage>(...)
rather thanbackend.store(...)
also fixes the error.This looks like a potential type inference bug, but it might be expected behavior. If it's expected, I think the error messages could use some work.
Meta
rustc --version --verbose
: