Covariance of Box<dyn FnOnce(T)> in rust - rust

I have a function that expects a short lived object. I would expect that I would be able to always pass it a long lived object. But I am getting a strange error when I try to encode that:
type F<'arg> = Box<dyn FnOnce(&'arg ())>;
fn contravar<'small, 'large: 'small>(f: F<'small>) -> F<'large> {
f
}
playground
Particularly:
error: lifetime may not live long enough
--> src/lib.rs:3:5
|
2 | fn contravar<'small, 'large: 'small>(f: F<'small>) -> F<'large> {
| ------ ------ lifetime `'large` defined here
| |
| lifetime `'small` defined here
3 | f
| ^ function was supposed to return data with lifetime `'large` but it is returning data with lifetime `'small`
|
= help: consider adding the following bound: `'small: 'large`
It seems like F is invariant for its argument but I would have guessed that it's contravariant. Am I missing something? Is there a way to make F<'arg> really contravariant for 'arg?
Edit: it looks like the "problem" is that rust wants to treat all generic traits the same (including Fn/FnMut/FnOnce). My opinion is that those 3 are and should be treated special especially given that they are the only way to refer to closures. For that reason I opened an issue

The Rust Reference's page on Subtyping and Variance documents that, as of Rust 1.63.0, fn(T) -> () is contravariant over T and that dyn Trait<T> + 'a is invariant over T.
FnOnce, FnMut and Fn are traits, so that means dyn FnOnce(&'a ()) is unfortunately invariant over &'a ().
// Compiles
pub fn contravariant<'a, 'b: 'a>(x: fn(&'a ())) -> fn(&'b ()) { x }
// Doesn't compile
pub fn contravariant2<'a, 'b: 'a>(x: Box<dyn FnOnce(&'a ())>) -> Box<dyn FnOnce(&'b ())> { x }
Is there a way to wrap FnOnce somehow to convince the compiler of the correct variance?
Here's what I could come up with using unsafe code. Note that I'm not making any guarantees as to whether this is sound or not. I don't know of any way to do this without unsafe code.
use std::marker::PhantomData;
trait Erased {}
impl<T> Erased for T {}
pub struct VariantBoxedFnOnce<Arg, Output> {
boxed_real_fn: Box<dyn Erased + 'static>,
_phantom_fn: PhantomData<fn(Arg) -> Output>,
}
impl<Arg, Output> VariantBoxedFnOnce<Arg, Output> {
pub fn new(real_fn: Box<dyn FnOnce(Arg) -> Output>) -> Self {
let boxed_real_fn: Box<dyn Erased + '_> = Box::new(real_fn);
let boxed_real_fn: Box<dyn Erased + 'static> = unsafe {
// Step through *const T because *mut T is invariant over T
Box::from_raw(Box::into_raw(boxed_real_fn) as *const (dyn Erased + '_) as *mut (dyn Erased + 'static))
};
Self {
boxed_real_fn,
_phantom_fn: PhantomData,
}
}
pub fn call_once(self, arg: Arg) -> Output {
let boxed_real_fn: Box<Box<dyn FnOnce(Arg) -> Output>> = unsafe {
// Based on Box<dyn Any>::downcast()
Box::from_raw(Box::into_raw(self.boxed_real_fn) as *mut Box<dyn FnOnce(Arg) -> Output>)
};
boxed_real_fn(arg)
}
}
pub fn contravariant<'a, 'b: 'a>(x: VariantBoxedFnOnce<&'a (), ()>) -> VariantBoxedFnOnce<&'b (), ()> { x }
#[cfg(test)]
mod tests {
use super::*;
fn foo(_x: &()) {}
#[test]
pub fn check_fn_does_not_require_static() {
let f = VariantBoxedFnOnce::new(Box::new(foo));
let x = ();
f.call_once(&x);
}
#[test]
pub fn check_fn_arg_is_contravariant() {
let f = VariantBoxedFnOnce::new(Box::new(foo));
let g = contravariant(f);
let x = ();
g.call_once(&x);
}
}
Here, VariantBoxedFnOnce is limited to functions taking one argument.
The trick is to store a type-erased version of the Box<dyn FnOnce(Arg) -> Output> such that Arg disappears, because we don't want the variance of VariantBoxedFnOnce<Arg, Output> to depend on Box<dyn FnOnce(Arg) -> Output> (which is invariant over Arg). However, there's also a PhantomData<fn(Arg) -> Output> field to provide the proper contravariance over Arg (and covariance over Output).
We can't use Any as our erased type, because only 'static types implement Any, and we have a step in VariantBoxedFnOnce::new() where we have a Box<dyn Erased + '_> where '_ is not guaranteed to be 'static. We then immediately "transmute" it into 'static, to avoid having a redundant lifetime parameter on VariantBoxedFnOnce, but that 'static is a lie (hence the unsafe code). call_once "downcasts" the erased type to the "original" Box<dyn FnOnce(Arg) -> Output>, except that Arg and Output may be different from the original due to variance.

Related

Borrow Checker not releasing borrow from FnOnce callback

Why does this compile:
fn func<T>(
callback: impl FnOnce(&mut i64) -> T,
) -> T {
let v = 42;
callback(&mut 42)
}
but this not?:
fn func<'a, T>(
callback: impl FnOnce(&'a mut i64) -> T,
) -> T {
let v = 42;
callback(&mut 42)
}
Even this doesn't compile:
fn func<'a, T: 'static>(
callback: impl FnOnce(&'a mut i64) -> T,
) -> T {
let v = 42;
callback(&mut 42)
}
Is there a way to tell Rust that the T returned from the callback doesn't hold any references to 'a? I thought : 'static would forbid references in general but it doesn't seem to work. Unfortunately, I do need to give 'a a name because I'm using the lifetime elsewhere, the actual code is somewhat more complicated than this minimal example.
In your first snippet, the lifetimes are elided via Higher-Ranked Trait Bounds:
fn func<T>(
callback: impl for<'a> FnOnce(&'a mut i64) -> T,
) -> T {
let v = 42;
callback(&mut 42)
}
That means the closure can be called with any lifetime, and that includes the lifetime of v.
In your second snippet, the lifetime is chosen by the caller. This is not related to T: for example, the caller can choose 'static, then store the parameter in a static. There is just no way to satisfy this requirement with a variable defined in the function.

One type (function pointer) is more general than the other

I reached this error in this example, and I'm trying to understand what's happening.
use futures::future::{BoxFuture, FutureExt};
use std::sync::Arc;
use futures::lock::Mutex;
struct Message {}
struct Client {}
enum Error {
ConnectionClosed,
}
#[derive(Default)]
struct MyType{}
impl Client {
fn send_and_expect<'a, R: 'a + Default + Send>(
client: Arc<Mutex<Self>>,
message: &'a Message,
) -> BoxFuture<'a, Result<R, Error>> {
async move { Ok(R::default()) }.boxed()
}
pub fn connection_retrier<'a, R: 'a + Default + Send>(
client: Arc<Mutex<Self>>,
f: for<'b> fn(Arc<Mutex<Self>>, &'b Message) -> BoxFuture<'b, Result<R, Error>>,
f_m: &'a Message,
) -> BoxFuture<'a, Result<R, Error>>
{
async move {Ok(R::default())}.boxed()
}
async fn send_with_retry<'a>(
client: Arc<Mutex<Self>>,
message: &'a Message,
) -> Result<MyType, Error> {
let client = Arc::new(Mutex::new(Client{}));
Self::connection_retrier::<MyType>(client, Self::send_and_expect, message)
.await
}
}
Error:
error[E0308]: mismatched types
--> src/lib.rs:36:52
|
36 | Self::connection_retrier::<MyType>(client, Self::send_and_expect, message)
| ^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected fn pointer `for<'b> fn(Arc<_>, &'b Message) -> Pin<Box<(dyn futures::Future<Output = std::result::Result<MyType, Error>> + std::marker::Send + 'b)>>`
found fn pointer `fn(Arc<_>, &Message) -> Pin<Box<dyn futures::Future<Output = std::result::Result<MyType, Error>> + std::marker::Send>>`
Playground
Well, connection_retrier requres a function f: for<'b> fn(Arc<Mutex<Self>>, &'b Message) -> BoxFuture<'b, Result<R, Error>>,. The one I passed seems to fit the signature.
The way I view for<'b> fn... is: I accept a function f for any lifetime 'b presented. So, for the particular lifetime 'a, send_and_expect seems to fit everything.
The only suspicion I have is for the lifetime of R being fixed at 'a in connection_retrier so the f with its for<'b> is not free to give any lifetime for its R. I think the lifetime of the R in f should be 'b. Is it possible to force f to have R with lifetime 'b?
Something like this:
f: for<'b> fn<RR: 'b>(Arc<Mutex<Self>>, &'b Message) -> BoxFuture<'b, Result<RR, Error>>,
This error looks to be caused by limitation of the compiler's ability to automatically convert generic functions which include a type bounded by a lifetime into a function pointer that is fully generic over that lifetime. I'm not quite sure why this limitation exists, but it can be worked around by wrapping the generic call in a non-generic closure. All you need to do to make your example compile is to change:
Self::send_and_expect
to
|c, m| Self::send_and_expect(c, m)
Thus hiding the generic from the compiler.

lifetime of function pointer is for<'a, '_> while it should be for<'r>

Sometimes I struggle with lifetimes. I'm still learning and I don't know what's happening here:
use std::future::Future;
use futures::future::{BoxFuture, FutureExt};
struct M{}
struct Client{}
impl Client {
async fn send_and_expect<'a>(
&'a mut self,
m: &M
) -> std::result::Result<(), ()> {
Ok(())
}
pub fn connection_retrier<'a, T>(
f: fn(&'a mut Self, &M) -> T,
f_self: &'a mut Self,
f_m: &'a M,
)-> BoxFuture<'a, std::result::Result<(),()>>
where
T: Future<Output = std::result::Result<(), ()>> + 'a
{
async move {
Client::send_and_expect(f_self, f_m).await
}.boxed()
}
async fn send_with_retry<'a>(&'a mut self) -> std::result::Result<(), ()> {
let m = M{};
Client::connection_retrier(
Client::send_and_expect,
&mut *self, &m).await
}
}
Error:
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/lib.rs:31:21
|
11 | ) -> std::result::Result<(), ()> {
| --------------------------- the `Output` of this `async fn`'s found opaque type
...
31 | Client::send_and_expect,
| ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected fn pointer `for<'r> fn(&mut Client, &'r M) -> impl futures::Future`
found fn pointer `for<'a, '_> fn(&'a mut Client, &M) -> impl futures::Future
Playground
I'm now completely confused about why in for<'r> fn(&mut Client, &'r M) -> impl futures::Future, &mut Client has no lifetime. And what does _ means in for<'a, '_> fn(&'a mut Client, &M) -> impl futures::Future`?
I'm very intersted in learning what's happening here.
Cleaning up the code
In order to see this code from the compiler's perspective, let's change all the lifetime parameters in this example to be explicit and have distinct names, expand the async fn sugar, and then look at how the error changes.
The above example is equivalent to the following after lifetime inference and desugaring.
// name the second lifetime, and expand the async fn sugar.
fn send_and_expect<'a, 'b>(&'a mut self, m: &'b M) -> impl Future<Item=Result<(), ()>> + 'a + 'b
{ ... }
// rename 'a to 'c to avoid ambiguous lifetime names
pub fn connection_retrier<'c, T>(
f: for<'d> fn(&'c mut Self, &'d M) -> T, // name the implicit higher-ranked lifetime here
f_self: &'c mut Self,
f_m: &'c M,
)-> BoxFuture<'c, std::result::Result<(), ()>>
where T: Future<Output = std::result::Result<(), ()>> + 'c
{ ... }
// rename 'a to 'e to avoid ambiguous lifetime names
async fn send_with_retry<'e>(&'e mut self) -> std::result::Result<(), ()> {
After making this change, the error becomes:
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/lib.rs:31:21
|
11 | ) -> std::result::Result<(), ()> {
| --------------------------- the `Output` of this `async fn`'s found opaque type
...
31 | Client::send_and_expect,
| ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected fn pointer `for<'d> fn(&mut Client, &'d M) -> impl futures::Future`
found fn pointer `for<'a, 'b> fn(&'a mut Client, &'b M) -> impl futures::Future`
This should clarify your question about '_: it's just the name the compiler gave the inferred second lifetime parameter of send_and_expect. As for the missing lifetime on &mut Client, you can see that it's still missing here. For reasons I do not completely understand, and in ways that depend on the exact error message given, the compiler will sometimes omit concrete lifetimes when printing the type of references, but make no mistake, the lifetime of that reference is 'c.
Solving the error
Onto the actual problem. The signature of f indicates that connection_retrier is expecting a function which (1) takes a reference of lifetime &'c and (2) a reference of any other lifetime, and (3) returns a Future type which will remain valid as long as 'c does, as specified by your where bound.
When we pass send_and_expect to connection_retrier, in order for the signatures to match the compiler is coercing it to the type for<'d> send_and_expect::<'c, 'd>. But that type doesn't meet condition (3) above! Unlike a regular function, the default behavior of an async function is to capture all input lifetimes in its return type, so the return type of for<'d> send_and_expect::<'c, 'd> is in fact impl Future<Item=Result<(), ()>> + 'c + 'd, as you can tell by looking at send_and_expect's expanded signature.
Since this type borrows from the two lifetimes 'c and 'd, and there is no constraint that 'd: 'c (read: 'd outlives 'c), it may not remain valid for the entirety of the lifetime 'c, if 'd ends first. It is this mismatch that results in the rather cryptic lifetime error you received. There are two ways you can solve this problem, depending on your preferred semantics. You can either:
Remove the higher-ranked bound from f entirely, and specify exactly the lifetimes you will be calling it with in connection_retrier (both &'c.)
pub fn connection_retrier<'c, T>(
f: fn(&'c mut Self, &'c M) -> T
f_self: &'c mut Self,
f_m: &'c M,
) -> BoxFuture<'c, std::result::Result<(), ()>>
where T: Future<Output = std::result::Result<(), ()>> + 'c
{ ... }
Keep the signature of connection_retrier the same and specify that the future returned by send_and_expect only borrows from its first argument. To do this, you will need to drop the async fn sugar on the signature and wrap the body in an async move block.
fn send_and_expect<'a, 'b>(&'a mut self, m: &'b M) -> impl Future<Output=Result<(), ()>> + 'a
{ async move { ... } }
Note that you cannot solve this by writing the type of f as for<'d: 'c> fn(&'c mut Self, &'d M) -> T, as bounds are currently not permitted for universally quantified lifetimes.

Storing FnMut in struct gives lifetime problems

I'm trying to store a FnMut in a struct:
struct OpenVPNSocket {
socket_send_callback: Option<Box<dyn FnMut(Vec<u8>) -> Result<(), ()>>>,
}
impl OpenVPNSocket {
fn set_socket_send<F: FnMut(Vec<u8>) -> Result<(), ()>>(&mut self, callback: Box<F>) {
self.socket_send_callback = Some(callback);
}
}
I get this error:
error[E0310]: the parameter type `F` may not live long enough
--> src/lib.rs:8:42
|
7 | fn set_socket_send<F: FnMut(Vec<u8>) -> Result<(), ()>>(&mut self, callback: Box<F>) {
| -- help: consider adding an explicit lifetime bound...: `F: 'static +`
8 | self.socket_send_callback = Some(callback);
| ^^^^^^^^ ...so that the type `F` will meet its required lifetime bounds
I understand lifetime as something to do with references. However I don't use references. I don't see why my struct cannot store a Box. A Box lives as long as it's used.
UPDATE:
I have this example:
use std::sync::Arc;
pub type OnConsume = Arc<dyn Fn() -> Option<u8> + Send + Sync>;
struct Test {
callback: OnConsume
}
impl Test {
fn set_on_consume(&mut self, f: OnConsume) {
self.callback = f;
}
}
which works. What is the difference from the previous one?
In Rust, values also have lifetimes. Take, for example, this struct:
struct RefWrapper<'a> {
some_ref: &'a u32
}
An instance of RefWrapper is not a reference, but contains a lifetime. Since you're moving the box into the struct, which could live for the duration of the program (the method makes no guarantees as to when the struct instance could be dropped), the function must live for the maximum lifetime, the static lifetime.
All trait objects have lifetimes, the default implicit lifetime for boxed trait objects is 'static so your struct's socket_send_callback actually has an implicit + 'static bound. Shown in context:
struct OpenVPNSocket {
socket_send_callback: Option<Box<dyn FnMut(Vec<u8>) -> Result<(), ()> + 'static>>,
}
Since the boxed trait object has to be bounded by a 'static lifetime when you write a function to set this field the value itself must have a 'static lifetime which is why the compiler suggests adding that explicit bound. Fixed example with added bound:
impl OpenVPNSocket {
// notice the added 'static bound for F
fn set_socket_send<F: FnMut(Vec<u8>) -> Result<(), ()> + 'static>(&mut self, callback: Box<F>) {
self.socket_send_callback = Some(callback);
}
}
With this change your code will compile. If you want to accept trait objects that aren't bounded by 'static lifetimes then you can do that by making your OpenVPNSocket generic over lifetimes. This alternative solution also compiles:
struct OpenVPNSocket<'a> {
socket_send_callback: Option<Box<dyn FnMut(Vec<u8>) -> Result<(), ()> + 'a>>,
}
impl<'a> OpenVPNSocket<'a> {
fn set_socket_send<F: FnMut(Vec<u8>) -> Result<(), ()> + 'a>(&mut self, callback: Box<F>) {
self.socket_send_callback = Some(callback);
}
}
The reason why this code works is because you define the type once and use it in multiple places, and in all places it has the implicit 'static bound. Desugared:
use std::sync::Arc;
pub type OnConsume = Arc<dyn Fn() -> Option<u8> + Send + Sync + 'static>;
struct Test {
callback: OnConsume
}
impl Test {
fn set_on_consume(&mut self, f: OnConsume) {
self.callback = f;
}
}
However you can do the exact same thing in your prior code as well:
type Callback = Box<dyn FnMut(Vec<u8>) -> Result<(), ()>>;
struct OpenVPNSocket {
socket_send_callback: Option<Callback>,
}
impl OpenVPNSocket {
fn set_socket_send(&mut self, callback: Callback) {
self.socket_send_callback = Some(callback);
}
}
The above also compiles, and the implicit 'static bound is still there.

Using higher-ranked trait bounds with generics

I've stumbled upon an interesting edge case: using higher-ranked lifetime bounds to accept closures that return generic parameters, such as for<'a> FnOnce(&'a T) -> R: MyTrait. There's no way to specify that R lives for at most 'a. Perhaps it's best to explain with an example.
Let's define a simple reference-like type wrapping a value:
struct Source;
struct Ref<'a> {
source: &'a Source,
value: i32,
}
For convenience, let's add a helper constructor. Here I will use explicit lifetimes to make the borrowing self-explanatory:
impl Source {
fn new_ref<'a>(&'a self, value: i32) -> Ref<'a> {
Ref { source: self, value }
}
}
This is an extremely fancy implementation of a integer copying routine using HRTBs with a closure over our Ref:
fn call_1<F>(callback: F) -> i32
where
for<'a> F: FnOnce(&'a Source) -> Ref<'a>,
{
let source = Source;
callback(&source).value
}
fn fancy_copy_1(value: i32) -> i32 {
call_1(|s| s.new_ref(value))
}
This is fine and is working as expected. We know that Ref does not outlive the Source and the compiler is also able to pick it up. Now let's create a simple trait and implement it for our reference:
trait MyTrait {
fn value(&self) -> i32;
}
impl<'a> MyTrait for Ref<'a> {
fn value(&self) -> i32 {
self.value
}
}
And modify our integer copying routine to return a generic type implementing that trait instead of just returning Ref:
fn call_2<R, F>(callback: F) -> i32
where
for<'a> F: FnOnce(&'a Source) -> R,
R: MyTrait,
{
let source = Source;
callback(&source).value()
}
fn fancy_copy_2(value: i32) -> i32 {
call_2(|s| s.new_ref(value))
}
Suddenly I get an error: cannot infer an appropriate lifetime for autoref due to conflicting requirements. Rust playground link for convenience. That actually makes sense from some perspective: unlike with Ref<'a> in the first example I never said that R has to live for at most 'a. could very well live longer and thus have access to freed memory. So I need to annotate it with it's own lifetime. But there's no place to do it! The first instinct is to put the lifetime in the bounds:
where
for<'a> F: FnOnce(&'a Source) -> R,
R: MyTrait + 'a,
which is of course incorrect, as 'a is only defined for the first bound.
This is where I got confused, started searching and never found anything about combining HRTBs and generic types in one bound. Perhaps more experienced people in Rust have any suggestions?
Upd 1.
While I was thinking about the problem some more, I remembered I could use the impl Trait syntax. This looks like a solution to my problem:
fn call_3<F>(callback: F) -> i32
where
F: for<'a> FnOnce(&'a Source) -> (impl MyTrait + 'a),
{
let source = Source;
callback(&source).value()
}
fn fancy_copy_3(value: i32) -> i32 {
call_3(|s| Box::new(s.new_ref(value)))
}
That, however does not work because impl MyTrait is not allowed in this place for some reason (probably temporarily). But that made me think of dyn Trait syntax and that indeed does work!
fn call_4<F>(callback: F) -> i32
where
F: for<'a> FnOnce(&'a Source) -> Box<dyn MyTrait + 'a>,
{
let source = Source;
let value = callback(&source); // note how a temporary is required
value.value()
}
fn fancy_copy_4(value: i32) -> i32 {
call_4(|s| Box::new(s.new_ref(value)))
}
Here's my solution: with dyn Trait syntax there is a place to put the + 'a! Unfortunately this solution still doesn't quite work for me too well, as it requires object-safety on the trait plus adds an overhead of allocating a boxed value. But at least it's something.

Resources