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


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/
9   |     executor: Box<dyn AgnostikExecutor>,
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `agnostik::AgnostikExecutor` cannot be made into an object
   ::: /home/dh/.cargo/registry/src/
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
    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
        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.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.