How to store returned "impl Trait" value if 'Trait' isn't object safe?

Hi,

I'd like to accept an optional parameter, which I will create if None is passed.

The problem is, the function that generates this value returns an "impl Trait" value which contains generic methods – which seems to prevent the creation of trait objects.

I'm wondering now what I can do to store such a value in a struct.

The following code demonstrates the problem:

use agnostik::{Agnostik, AgnostikExecutor};

fn main() {
    let store1 = setup1(None);
}

struct Store1 {
    executor: Box<dyn AgnostikExecutor>,
}

fn setup1(executor: Option<impl AgnostikExecutor>) -> Store1 {
    let executor = Box::new(executor.unwrap_or_else(|| Agnostik::bastion()));
    Store1 {executor}
}

And this is the error I get:

  Compiling rust v0.1.0 (/home/dh/projects/play/rust)
error[E0038]: the trait `agnostik::AgnostikExecutor` cannot be made into an object
   --> src/bin/agnostik_executor.rs:9:5
    |
9   |     executor: Box<dyn AgnostikExecutor>,
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `agnostik::AgnostikExecutor` cannot be made into an object
    | 
   ::: /home/dh/.cargo/registry/src/github.com-1ecc6299db9ec823/agnostik-0.1.5/src/lib.rs:165:8
    |
165 |     fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
    |        ----- the trait cannot be made into an object because method `spawn` has generic type parameters
...
171 |     fn spawn_blocking<F, T>(&self, task: F) -> JoinHandle<T>
    |        -------------- the trait cannot be made into an object because method `spawn_blocking` has generic type parameters
...
177 |     fn block_on<F>(&self, future: F) -> F::Output
    |        -------- the trait cannot be made into an object because method `block_on` has generic type parameters

error: aborting due to previous error

For more information about this error, try `rustc --explain E0038`.
error: could not compile `rust`.

To learn more, run the command again with --verbose.

I tried two other approaches, which both failed:

A generic struct:

use agnostik::{Agnostik, AgnostikExecutor};

fn main() {
    let store2 = setup2(None);
}

struct Store2<E: AgnostikExecutor> {
    executor: E,
}

fn setup2<E: AgnostikExecutor>(executor: Option<E>) -> Store2<E> {
    let executor = executor.unwrap_or_else(|| Agnostik::bastion());
    Store2 {executor}
}

Which failes with "expected type parameter E, found opaque type" in setup2 (full error message).

I also tried to use "impl AgnostikExecutor" as the type of the field of the struct:

struct Store3 {
    executor: impl AgnostikExecutor,
}

But, unfortunately, impl Trait isn't allowed there...

So my question is how do I store an "impl Trait" value that isn't object safe because the trait contains generic methods?

You don't, you design your traits so that they are object safe if a user might want to store them as such, e.g. futures::task::Spawn is object-safe then the nice generic interface to it is SpawnExt::spawn.

You might be able to do something pretty horrible like storing a executor: Box<dyn FnMut(Pin<Box<dyn Future<Output = ()>>>)> which internally captures the AgnosticExecutor and monomorphizes its spawn function down to spawn::<Pin<Box<dyn Future<Output = ()>>>>, but then you're likely paying the cost of double-boxing your futures.

This is pretty much the right approach, but you’re running into the problem that Agnostik::bastion is a different type than E, and so the return type can’t be determined at compile time. You can solve this with an enum (untested):

enum ExecutorChoice<E:AgnostikExecutor> {
    Given(E), Fallback(BastionExecutor)
}

impl<E> AgnostikExecutor for ExecutorChoice<E> {
    // bounds copied from https://docs.rs/agnostik/0.1.5/agnostik/trait.AgnostikExecutor.html
    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static {
        match self {
            Self::Given(ref e) => e.spawn(future),
            Self::Fallback(ref e) => e.spawn(future)
        }
    }

    /* similar for spawn_blocking and block_on */
}


fn setup2<E: AgnostikExecutor>(executor: Option<E>) -> Store2<impl AgnostikExecutor> {
    let executor = match executor {
        Some(e) => ExecutorChoice::Given(e),
        None => ExecutorChoice::Fallback(Agnostik::bastion());
    }
    Store2 {executor}
}

Thanks, @Nemo157 and @2e71828.

After looking at the source of Agnostik::bastion(), I saw it just calles executors::BastionExecutor::new().

This realization plus restructuring my code solved my problem.