It bugged me that using the trait-variant crate, once a Send version is provided, you can not use its local variant without Send bounds anymore. This is my attempt to address this issue:
use ::core::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
pub trait LocalFoo {
type Output;
fn foo(&self) -> impl Future<Output = Self::Output>;
}
pub trait Foo: LocalFoo {
fn foo(&self) -> impl Future<Output = Self::Output> + Send;
}
impl<T> Foo for T
where
T: LocalFoo + Sync,
<T as LocalFoo>::Output: Send,
{
fn foo(&self) -> impl Future<Output = Self::Output> + Send {
// Considering the given trait bounds,
// is forcing Send on this Future sound?
// Are there any other issues?
UnsafeSendFuture(<Self as LocalFoo>::foo(self))
}
}
// Helper struct to force Send on a Future.
// I also tried core::mem::transmute but
// it can not infer its impl dst type.
struct UnsafeSendFuture<T>(T);
unsafe impl<T> Send for UnsafeSendFuture<T> where Self: Future {}
impl<T> Future for UnsafeSendFuture<T>
where
T: Future,
<T as Future>::Output: Send,
{
type Output = T::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe { self.map_unchecked_mut(|UnsafeSendFuture(future)| future) }.poll(cx)
}
}
You could implement Foo only for types whose LocalFoo::foo future is Send to begin with:
pub trait LocalFoo {
type Output;
type Fut: Future<Output = Self::Output>;
fn foo(&self) -> Self::Fut;
}
pub trait Foo {
type Output;
type Fut: Future<Output = Self::Output> + Send;
fn foo(&self) -> Self::Fut;
}
impl<T> Foo for T
where
T: LocalFoo,
<T as LocalFoo>::Fut: Send,
{
type Output = <T as LocalFoo>::Output;
type Fut = <T as LocalFoo>::Fut;
fn foo(&self) -> Self::Fut {
<Self as LocalFoo>::foo(self)
}
}
The example causes a second trait called IntFactory to be created. Implementers of the trait can choose to implement the variant instead of the original trait. The macro creates a blanket impl which ensures that any type which implements the variant also implements the original trait.
Notice the last sentence. That blanket implementation does exactly what you want. Demo with your particular trait definition:
Let me give a little more context on where this is coming from. The issue I was facing is when you provide a generic impl of Foo with some trait bounds which, because of Foo's Send requirement, need to be Send as well. So far so good. Unfortunately, the localtrait-variant version of that impl keeps the Send bounds even though in principle the very same impl block could work without Send bounds and support many more types.
So my idea was to flip the approach by providing a broad, not Send bounded, impl first and then reuse that very same impl in the more restrict world of Send.
Can you provide sample code that tries to do the thing you want to just work, but doesn't compile? Not trying to work around the problem at all, but demonstrating it?
Sure! I want a generic impl of the LocalFoo trait (in this case core::ops::Not) to be sound for Send, with the trivially same impl.
fn main() {
let cell = Cell::new(false);
let local_foo = !Wrapper(&cell);
_ = LocalFoo::foo(&local_foo);
// Does not compile because of the Cell in local_foo.
// Thats what we want!
// _ = Foo::foo(local_foo);
let foo = !Wrapper(false);
_ = LocalFoo::foo(&foo);
// Does compile, bool is Send.
// And its reusing the impl from LocalFoo!
_ = Foo::foo(&foo);
}
I understand the problem now. You can't use trait_variant's strategy because you can't make which trait appears in the generic implementation conditional on whether the future is Send.
But your unsafe-using impl is not sound, because in
impl<T> Foo for T
where
T: LocalFoo + Sync,
<T as LocalFoo>::Output: Send,
{
fn foo(&self) -> impl Future<Output = Self::Output> + Send {
UnsafeSendFuture(<Self as LocalFoo>::foo(self))
}
}
the Sendness of the output has nothing to do with the Sendness of the future, which is what you're unsafely asserting. Unfortunately, the syntax for doing this correctly, return type notation, is unstable and incomplete, but it does compile on nightly today:
#![feature(return_type_notation)]
impl<T> Foo for T
where
T: LocalFoo<foo(..): Send, Output: Send> + Sync,
{
fn foo(&self) -> impl Future<Output = Self::Output> + Send {
<Self as LocalFoo>::foo(self)
}
}
(I'm also using “associated type bounds”, LocalFoo<Output: Send>, which is stable as of 1.79.0. RTN is the extension of this sort of thing to naming the return type of a function on the left side of a bound.)