Storing a list of closures of the form `dyn FnMut() -> dyn Display>`

Basically I want to something like the following to work. In order to do this I need to store a list of closures with different possible return values. All I care about is that the return value is Display.

fn main() {
    let mut input = vec![0; 10];
    let mut ex = Example::default();
    ex.push(|| 5);
    ex.push(|| "hello");
    ex.push(|| { input.reverse(); input[0] });
}

First attempt

use std::fmt::Display;

#[derive(Default)]
pub struct Example {
    funcs: Vec<Box<dyn FnMut() -> dyn Display>>,
}

impl Example {
    pub fn push(&mut self, func: dyn FnMut() -> dyn Display) {
        self.funcs.push(Box::new(func))
    }
}

I quickly realized that this had multiple problems:

  • Not being able to pass a closure easily to .push().
  • func arg is not Sized

Second attempt, generics

use std::fmt::Display;

#[derive(Default)]
pub struct Example {
    funcs: Vec<Box<dyn FnMut() -> dyn Display>>,
}

impl Example {
    pub fn push<F>(&mut self, func: F)
    where
        F: FnMut() -> (dyn Display) + 'static
    {
        self.funcs.push(Box::new(func))
    }
}

This doesn't work:

the size for values of type `(dyn std::fmt::Display + 'static)` cannot be known at compilation time

I tried to add a Sized constraint but couldn't seem to get it to work. Maybe there is a way?

Third attempt, more generics

use std::fmt::Display;

#[derive(Default)]
pub struct Example {
    funcs: Vec<Box<dyn FnMut() -> dyn Display>>,
}

impl Example {
    pub fn push<F, R>(&mut self, func: F)
    where
        R: Display,
        F: FnMut() -> R
    {
        self.funcs.push(Box::new(func))
    }
}

This doesn't work

expected trait object `dyn std::fmt::Display`, found type parameter `R`

Is it possible to cast a type parameter that implements the correct types to a trait object?

Wrapper type

use std::fmt;

struct Display<'a> {
    inner: Box<dyn fmt::Display + 'a>,
}

impl fmt::Display for Display<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.inner.as_ref().fmt(f)
    }
}

#[derive(Default)]
pub struct Example<'a> {
    funcs: Vec<Box<dyn FnMut() -> Display<'a> + 'a>>,
}

impl<'a> Example<'a> {
    pub fn push<F, R>(&mut self, mut func: F)
    where
        R: fmt::Display + 'a,
        F: FnMut() -> R + 'a,
    {
        self.funcs.push(Box::new(move || Display { inner: Box::new(func()) }))
    }
}

This works, but it now seems quite complicated. Is there anyway to do this without the wrapper type? Or a better way entirely?

You can make the return type of the FnMut be Box<dyn Display>.

#[derive(Default)]
pub struct Example {
    funcs: Vec<Box<dyn FnMut() -> Box<dyn Display>>>,
}

The closest I could come up with uses FnOnce instead of FnMut:

fn main() {
    let mut input = vec![0; 10];
    let mut ex = Example::default();
    ex.push(|| &5);
    ex.push(|| &"hello");
    ex.push(|| { input.reverse(); &input[0] });
}

use std::fmt::Display;

#[derive(Default)]
pub struct Example<'out> {
    funcs: Vec<Box<dyn 'out + FnOnce() -> &'out dyn Display>>,
}

impl<'out> Example<'out> {
    pub fn push(&mut self, func: impl 'out + FnOnce() -> &'out dyn Display) {
        self.funcs.push(Box::new(func))
    }
}

Playground

Thanks, :person_facepalming: I think I tried this but using only 'static, but in doing the wrapper way I figured out that I had to use lifetimes. I.e. the following doesn't work

#[derive(Default)]
pub struct Example {
    funcs: Vec<Box<dyn FnMut() -> Box<dyn Display>>>,
}

impl Example {
    pub fn push<F, R>(&mut self, mut func: F)
    where
        R: Display,
        F: FnMut() -> R,
    {
        self.funcs.push(Box::new(move || Box::new(func())))
    }
}

But this does


#[derive(Default)]
pub struct Example<'a> {
    funcs: Vec<Box<dyn FnMut() -> Box<dyn Display + 'a> + 'a>>,
}

impl<'a> Example<'a> {
    pub fn push<F, R>(&mut self, mut func: F)
    where
        R: Display + 'a,
        F: FnMut() -> R + 'a,
    {
        self.funcs.push(Box::new(move || Box::new(func())))
    }
}
2 Likes

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.