Ivan Raul Sanchez Diaz
Ivan Raul Sanchez Diaz

Reputation: 351

why can't i pass an async function as a field when it takes mutable references?

I was working on a structure that had asynchronous functions as fields, but it didn't work when it took mutable references.

this works:

#![allow(dead_code)]

pub struct TestStruct;

struct TestStruct2<F>
where
    F: std::future::Future<Output = ()>,
{
    foo: fn(TestStruct) -> F,
}

async fn foo(_x: TestStruct) {}

#[tokio::main]
async fn main() {
    let _s = TestStruct2 { foo };
}

this does not work:

#![allow(dead_code)]

pub struct TestStruct;

struct TestStruct2<F>
where
    F: std::future::Future<Output = ()>,
{
    foo: fn(&mut TestStruct) -> F,
}

async fn foo(_x: &mut TestStruct) {}

#[tokio::main]
async fn main() {
    let _s = TestStruct2 { foo }; //error here
}

error:

mismatched types
expected fn pointer `for<'r> fn(&'r mut TestStruct) -> _`     
found fn item `for<'r> fn(&'r mut TestStruct) -> impl std::future::Future<Output = ()> {foo}`

could someone explain to me why this happens and what can I do about it?

Upvotes: 2

Views: 622

Answers (2)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65822

It looks like foo's return type has an implicit + 'a bound, where 'a is the anonymous lifetime in &mut TestStruct.

Simply desugaring foo to an explicit impl Trait type and an async block solves the issue.

#![allow(dead_code)]

pub struct TestStruct;

struct TestStruct2<F>
where
    F: std::future::Future<Output = ()>,
{
    foo: fn(&mut TestStruct) -> F,
}

fn foo(_x: &mut TestStruct) -> impl std::future::Future<Output = ()> {
    async {}
}

#[tokio::main]
async fn main() {
    let _s = TestStruct2 { foo };
}

However, as soon as we try to use the reference from within the async block, errors pop up again. (It looks like the compiler moves all parameters from an async fn to the hidden async block.)

fn foo(_x: &mut TestStruct) -> impl std::future::Future<Output = ()> {
    async {
        _x;
    }
}
error[E0700]: hidden type for `impl Future<Output = ()>` captures lifetime that does not appear in bounds
  --> src/main.rs:13:5
   |
12 |   fn foo(_x: &mut TestStruct) -> impl std::future::Future<Output = ()> {
   |              --------------- hidden type `impl Future<Output = ()>` captures the anonymous lifetime defined here
13 | /     async {
14 | |         _x;
15 | |     }
   | |_____^
   |
help: to declare that the `impl Trait` captures `'_`, you can add an explicit `'_` lifetime bound
   |
12 | fn foo(_x: &mut TestStruct) -> impl std::future::Future<Output = ()> + '_ {
   |                                                                      ++++

And if we apply the compiler's suggestion, then we're back to square one:

fn foo(_x: &mut TestStruct) -> impl std::future::Future<Output = ()> + '_ {
    async {
        _x;
    }
}
error[E0308]: mismatched types
  --> src/main.rs:20:28
   |
20 |     let _s = TestStruct2 { foo };
   |                            ^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'r> fn(&'r mut TestStruct) -> _`
                 found fn item `for<'r> fn(&'r mut TestStruct) -> impl for<'r> Future<Output = ()> {foo}`

We can fix this by adding a lifetime parameter to TestStruct2 and using it in the appropriate places. (We can also restore foo to its original form.)

#![allow(dead_code)]

pub struct TestStruct;

struct TestStruct2<'a, F>
where
    F: std::future::Future<Output = ()> + 'a,
{
    foo: fn(&'a mut TestStruct) -> F,
}

async fn foo(_x: &mut TestStruct) {}

#[tokio::main]
async fn main() {
    let s = TestStruct2 { foo };
    let mut t = TestStruct;
    (s.foo)(&mut t).await;
}

Upvotes: 3

Dan Getz
Dan Getz

Reputation: 9152

The issue isn't the mut, it's the reference and its lifetime. According to the Asynchronous Programming in Rust book, the return values of async functions have their lifetimes bounded by their arguments' lifetimes. Your definition of TestStruct2::foo doesn't accept this. To fix that, you can add this lifetime explicitly to F:

struct TestStruct2<'a, F>
where
    F: std::future::Future<Output = ()> + 'a,
{
    foo: fn(&'a mut TestStruct) -> F,
}

Upvotes: 3

Related Questions