How do I store a variable of type `impl Trait` in a struct?
Asked Answered
T

2

12

This works:

let fut = Arc::new(Mutex::new(Box::pin(async { 1 })));

let mut conn_futures = BTreeMap::new(); // implicitly typed
conn_futures.insert(123, fut);
if let Some(fut) = conn_futures.get_mut(&123) {
   let fut = fut.clone();
   self.pool.spawn(async move {
        let mut fut = fut.try_lock().unwrap();
        (&mut *fut).await;
    });
};

How do I write the same thing inside a structure; what is the type of conn_futures? According to the compiler, it's BTreeMap<i32, impl Future>, but there's no way to write that in a structure:

struct Foo {
    conn_futures: BTreeMap<i32, impl Future>, // impl not allow in this position
}

I tried this:

use futures::{executor::LocalPool, lock::Mutex, task::SpawnExt, Future}; // 0.3.1
use std::{collections::BTreeMap, pin::Pin, sync::Arc};

struct Foo {
    conn_futures: BTreeMap<i32, Arc<Mutex<Pin<Box<dyn Future<Output = i32>>>>>>,
}

fn alternative() {
    let mut pool = LocalPool::new();
    let spawner = pool.spawner();

    // Have a structure with the btreemap instead
    let mut foo = Foo {
        conn_futures: BTreeMap::new(),
    };
    let fut = Arc::new(Mutex::new(Box::pin(async { 1 })));
    foo.conn_futures.insert(123, fut);
    if let Some(fut) = foo.conn_futures.get_mut(&123) {
        let fut = fut.clone();
        spawner.spawn(async move {
            let mut fut = fut.try_lock().unwrap();
            (&mut *fut).await;
        });
    };
}

fn main() {
    let mut pool = LocalPool::new();
    let spawner = pool.spawner();
    let fut = Arc::new(Mutex::new(Box::pin(async { 1 })));

    let mut conn_futures = BTreeMap::new(); // implicitly typed
    conn_futures.insert(123, fut);
    if let Some(fut) = conn_futures.get_mut(&123) {
        let fut = fut.clone();
        spawner.spawn(async move {
            let mut fut = fut.try_lock().unwrap();
            (&mut *fut).await;
        });
    };
}

Playground

And got an error

error[E0308]: mismatched types
  --> src/main.rs:17:34
   |
17 |     foo.conn_futures.insert(123, fut);
   |                                  ^^^ expected trait core::future::future::Future, found opaque type
   |
   = note: expected type `std::sync::Arc<futures_util::lock::mutex::Mutex<std::pin::Pin<std::boxed::Box<(dyn core::future::future::Future<Output = i32> + 'static)>>>>`
              found type `std::sync::Arc<futures_util::lock::mutex::Mutex<std::pin::Pin<std::boxed::Box<impl core::future::future::Future>>>>`

How do I declare the type of conn_futures in a struct?

Then answered 25/11, 2019 at 15:40 Comment(3)
impl just means "any type that implements this trait". To store it in a struct or enum, you have 3 options: 1) make the stuct generic, e.g. struct Foo<T: Future>, 2) use the concrete type that implements the trait, 3) use a trait object, e.g. Box<dyn Future>Apiece
In this example (2) or (3) don't seem to work. Did you manage to get it to work on the playground? And 1 would leak out details of an internal implementation to the outside worldThen
For Future option (2) doesn't work if the future is from an async block. But option (3) works. Just cast the result of Box::pin to Pin<Box<dyn Future<Output = i32>>>Apiece
F
16

You cannot, really. impl Trait creates an anonymous, unnameable type. That means that you cannot declare a variable with an explicit type that will work.

The primary solution is to use a trait object:

use std::fmt::Display;

fn make_it() -> impl Display {
    2
}

struct Example {
    it: Box<dyn Display>,
}

impl Example {
    fn make() -> Self {
        Example {
            it: Box::new(make_it()),
        }
    }
}

You can also avoid using an associated function and use a plain function instead, coupled with a generic:

use std::fmt::Display;

fn make_it() -> impl Display {
    2
}

struct Example<T> {
    it: T,
}

impl Example<Box<dyn Display>> {
    fn make() -> Self {
        Example {
            it: Box::new(make_it()),
        }
    }
}

fn make_example() -> Example<impl Display> {
    Example {
        it: make_it(),
    }
}

Nightly only

If you wish to use unstable nightly features, you can use existential types (RFC 2071):

// 1.51.0-nightly (2021-01-03 80184183ba0a53aa4f49)
#![feature(type_alias_impl_trait)]

use std::fmt::Display;

type SomeDisplay = impl Display;

fn make_it() -> SomeDisplay {
    2
}

struct Example {
    it: SomeDisplay,
}

impl Example {
    fn make() -> Self {
        Example { it: make_it() }
    }
}

Or:

// 1.51.0-nightly (2021-01-03 80184183ba0a53aa4f49)
#![feature(type_alias_impl_trait)]

use std::fmt::Display;

fn make_it() -> impl Display {
    2
}

struct Example<T> {
    it: T,
}

type SomeDisplay = impl Display;

impl Example<SomeDisplay> {
    fn make() -> Self {
        Example { it: make_it() }
    }
}

See also:

Flowers answered 25/11, 2019 at 16:2 Comment(0)
T
-1

Though the suggestions above are useful, the specific answer to the question is to cast the Pin<Box<Future>>> appropriately

This line

let fut = Arc::new(Mutex::new(Box::pin(async { 1 })));

needs to change

let fut = Arc::new(Mutex::new(Box::pin(async { 1 }) as Pin<Box<Future<Output=i32>>> ));

which will allow one to express the following struct

struct Foo {
    conn_futures: BTreeMap<ChannelId, Arc<Mutex<Pin<Box<dyn Future<Output = i32>>>>>>,
}

and the compiler won't complain. Thanks @Aloso for the hint

However, the following error is given instead

error[E0277]: `(dyn core::future::future::Future<Output = i32> + 'static)` cannot be sent between threads safely
  --> src/main.rs:24:16
   |
24 |        spawner.spawn(async move {
   |                ^^^^^ `(dyn core::future::future::Future<Output = i32> + 'static)` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `(dyn core::future::future::Future<Output = i32> + 'static)`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::ptr::Unique<(dyn core::future::future::Future<Output = i32> + 'static)>`
   = note: required because it appears within the type `std::boxed::Box<(dyn core::future::future::Future<Output = i32> + 'static)>`
   = note: required because it appears within the type `std::pin::Pin<std::boxed::Box<(dyn core::future::future::Future<Output = i32> + 'static)>>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `futures_util::lock::mutex::Mutex<std::pin::Pin<std::boxed::Box<(dyn core::future::future::Future<Output = i32> + 'static)>>>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<futures_util::lock::mutex::Mutex<std::pin::Pin<std::boxed::Box<(dyn core::future::future::Future<Output = i32> + 'static)>>>>`
   = note: required because it appears within the type `[static generator@src/main.rs:24:33: 27:10 fut:std::sync::Arc<futures_util::lock::mutex::Mutex<std::pin::Pin<std::boxed::Box<(dyn core::future::future::Future<Output = i32> + 'static)>>>> _]`
   = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:24:33: 27:10 fut:std::sync::Arc<futures_util::lock::mutex::Mutex<std::pin::Pin<std::boxed::Box<(dyn core::future::future::Future<Output = i32> + 'static)>>>> _]>`
   = note: required because it appears within the type `impl core::future::future::Future`

which will be a separate question

Then answered 25/11, 2019 at 17:8 Comment(1)
Please pay attention to the compiler warnings about this code and adjust it accordingly.Flowers

© 2022 - 2024 — McMap. All rights reserved.