A struct with a boxed closure

I’m trying to create a struct that holds boxed closures. This is a simplification of what I’m trying to do:

struct FuncBox(Box<FnMut()>);

#[test]
fn runs() {
    use std::io::Write;
    let mut buffer = Vec::new();

    let mut my_box = FuncBox(Box::new(|| {
        write!(&mut buffer, "A")
        .expect("Could not write to buffer");
    }));

    (my_box.0)();

    assert_eq!(buffer, b"A");
}

which intuitively should compile fine, since buffer is immutably
borrowed in the last line after the last invocation of my_box, thus
rustc should (maybe?) statically see that the mutable borrow of the
closure has become irrelevant. But there seems to be an issue with
lifetimes.

I’ve tried it in various alternative ways. The one that baffles me the
most is when I don’t use FuncBox at all around the boxed closure of
my_box. In that case the test passes.

What is the difference between these two cases? Why doesn’t the first
work? (I need the first to work; I want to build an array of closures
that is manipulated at runtime).

Thank you very much for your time!

By default specified boxed types are `static. Easy fix.

struct FuncBox<'a>(Box<dyn FnMut() + 'a>);

This is also a case where the type does not give the information statically since it is a trait object and could perform some action on drop. ie NLL Rust 2018 edition does not let it go until the end of scope.
So also have to add scope block for the let. OR switch type;

struct FuncBox<'a, T: FnMut() + 'a>(Box<T>, PhantomData<&'a ()>);

Thanks a lot for the reply! So, this works:

pub struct FuncBox<'a>(Box<dyn FnMut() + 'a>);

#[test]
fn runs() {
    use std::io::Write;
    let mut buffer = Vec::new();

    {
        let mut my_box = FuncBox(Box::new(|| {
            write!(&mut buffer, "A")
            .expect("Could not write to buffer");
        }));

        (my_box.0)();
    }

    assert_eq!(buffer, b"A");
}

However the nested scope seems too much like a trick to appease the compiler. I’d like to make this work without it. I guess I don’t know how to use PhantomData properly. Can you give a hand?

Also note that I don’t want to use generics, because eventually I want to build a struct that holds a vector of closures. Given that closures are all of different types, dyn must be used and not a generic type (if I’m understanding correctly).

PhantomData is closer to a compiler trick than a new scope, but this is essentially how you can use it:

struct Foo<T>{}

is invalid because you never use the type <T>. So you attach a PhantomData to make it valid:

struct Foo<T> (PhantomData<T>);

Therefore, it now contains a struct that uses a <T> (But doesn’t actually contain a <T> in memory in any form)
Same idea with lifetimes:

struct Foo<'a> {}

is invalid for the same reason as with <T>; it has no use in the struct and should therefore not be included. Therefore you can store a PhantomData that uses a 'a:

struct Foo<'a> (PhantomData<'a>);

but that doesn’t work because you need to provide a type and not a lifetime, so you just provide the most basic type that uses a lifetime: a reference. (To a unit struct ("()" for simplicity):

struct Foo<'a> (PhantomData<&'a ()>);

And so now the compiler is happy!


To apply the above to your struct (Slightly modified):

struct FuncBox<'a, T: FnMut() + Sized + 'a>(T);

'a is nowhere to be seen inside the declaration of FuncBox so we put it into a PhantomData:

struct FuncBox<'a, T: FnMut() + Sized + 'a>(T, PhantomData<&'a ()>);
1 Like

Thanks for the clear explanation. Especially the part of why we need <&'a ()> was enlightening.

However, generics don’t work for what I want. Here is a clearer example. The commented code changes my_box to another closure, so it won’t work if you uncomment:

pub struct FuncBox<T: FnMut()>(Box<T>);

#[test]
fn runs() {
    use std::io::Write;
    let mut buffer = Vec::new();

    let mut my_box = FuncBox(Box::new(|| {
        write!(&mut buffer, "A")
        .expect("Could not write to buffer");
    }));

    (my_box.0)();

//    my_box = FuncBox(Box::new(|| {
//        write!(&mut buffer, "B")
//        .expect("Could not write to buffer");
//    }));
//
//    (my_box.0)();

    assert_eq!(buffer[0], b'A');
//    assert_eq!(buffer[1], b'B');
}

Note that this doesn’t even need PhantomData.

This is why I need dyn Trait: to be able to pass several closures. However, I can’t get it to work. Is the nested scope the only way?

You could drop the boxed closure, which is equivalent.

  pub struct FuncBox<'a>(Box<dyn FnMut() + 'a>);

  #[test]
  fn runs() {
    use std::io::Write;
    use std::mem::drop;

    let mut buffer = Vec::new();

    let mut my_box = FuncBox(Box::new(|| {
      write!(&mut buffer, "A")
        .expect("Could not write to buffer");
    }));

    (my_box.0)();

    drop(my_box);

    assert_eq!(buffer, b"A");
  }

Unfortunately the following doesn’t work either. I think it has to do with the fact that the compiler doesn’t run code and thus doesn’t understand that my_box is dropped.

pub struct FuncBox<'a>(Box<dyn FnMut() + 'a>);

#[test]
fn runs() {
    use std::io::Write;
    use std::mem;

    let mut buffer = Vec::new();

    let mut my_box = FuncBox(Box::new(|| {
        write!(&mut buffer, "A")
        .expect("Could not write to buffer");
    }));

    (my_box.0)();

    mem::drop(my_box);

    my_box = FuncBox(Box::new(|| {
        write!(&mut buffer, "B")
        .expect("Could not write to buffer");
    }));

    (my_box.0)();

    mem::drop(my_box);

    assert_eq!(buffer[0], b'A');
    assert_eq!(buffer[1], b'B');
}

This is because the lifetime parameter is statically fixed w.r.t its type; contrary to what one would imagine, when you reassign to my_box, since it is a mutation assignment instead of new declaration assignment, the type remains the same, and thus so does the lifetime inferred by the compiler:

  • the borrow &mut buffer starts with the my_box declaration,

  • and must hold down to the last usage of it, at the second (my_box.0)() call;

Hence the conflict.

If you use let mut my_box = ... for the second assignment (i.e., if you shadow it with a new variable) the code compiles fine.

If you intend to keep a closure alive but unusued for “long periods of time”, then I suggest you use a shared borrow with interior mutability instead. For instance:

pub
struct FuncBox<'env> (Box<dyn Fn() + 'env>);

#[test]
fn runs ()
{
    use ::std::io::Write;
    use ::std::mem;

    let buffer = ::std::cell::RefCell::new(Vec::new());

    let mut my_box = FuncBox(Box::new(|| {
        write!(buffer.borrow_mut(), "A")
            .expect("Could not write to buffer");
    }));

    (my_box.0)();

    my_box = FuncBox(Box::new(|| {
        write!(buffer.borrow_mut(), "B")
            .expect("Could not write to buffer");
    }));

    (my_box.0)();

    assert_eq!(buffer.borrow()[0], b'A');
    assert_eq!(buffer.borrow()[1], b'B');
}
1 Like

Thank you very much! This worked like a charm.

I’m glad for all the help you folks have given me. Rust indeed has an amazing community.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.