1

In Rust, I'm trying to create a list of callbacks functions to invoke later:

use std::vec::Vec;

fn add_to_vec<T: FnMut() -> ()>(v: &Vec<Box<FnMut() -> ()>>, f: T) {
    v.push(Box::new(f));
}

fn call_b() {
    println!("Call b.");
}

#[test]
fn it_works() {
    let calls: Vec<Box<FnMut() -> ()>> = Vec::new();

    add_to_vec(&calls, || { println!("Call a."); });
    add_to_vec(&calls, call_b);

    for c in calls.drain() {
        c();
    }
}

I'm mostly following the advice here on how to store a closure, however, I'm still seeing some errors:

src/lib.rs:6:12: 6:23 error: the parameter type `T` may not live long enough [E0311]
src/lib.rs:6     v.push(Box::new(f));
                        ^~~~~~~~~~~
src/lib.rs:6:23: 6:23 help: consider adding an explicit lifetime bound for `T`
src/lib.rs:5:68: 7:2 note: the parameter type `T` must be valid for the anonymous lifetime #1 defined on the block at 5:67...
src/lib.rs:5 fn add_to_vec<T: FnMut() -> ()>(v: &Vec<Box<FnMut() -> ()>>, f: T) {
src/lib.rs:6     v.push(Box::new(f));
src/lib.rs:7 }
src/lib.rs:6:12: 6:23 note: ...so that the type `T` will meet its required lifetime bounds
src/lib.rs:6     v.push(Box::new(f));
                        ^~~~~~~~~~~

I've tried changing the function signature to:

fn add_to_vec<'a, T: FnMut() -> ()>(v: &Vec<Box<FnMut() -> ()>>, f: &'a T) {

… but this gets me:

src/lib.rs:6:12: 6:23 error: the trait `core::ops::Fn<()>` is not implemented for the type `&T` [E0277]
src/lib.rs:6     v.push(Box::new(f));
                        ^~~~~~~~~~~
error: aborting due to previous error
src/lib.rs:6:12: 6:23 error: the trait `core::ops::Fn<()>` is not implemented for the type `&T` [E0277]
src/lib.rs:6     v.push(Box::new(f));
                        ^~~~~~~~~~~
src/lib.rs:18:24: 18:51 error: mismatched types:
 expected `&_`,
    found `[closure src/lib.rs:18:24: 18:51]`
(expected &-ptr,
    found closure) [E0308]
src/lib.rs:18     add_to_vec(&calls, || { println!("Call a."); });
                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~

(The last error I can correct by adding a &; while I think this is something I should need, because add_to_vec is going to end up owning the closure, and thus needs to borrow it, I'm not entirely sure.)

Community
  • 1
  • 1
Thanatos
  • 42,585
  • 14
  • 91
  • 146

1 Answers1

3

There are a few problems with your code. Here’s a fully fixed version to begin with:

use std::vec::Vec;

fn add_to_vec<'a, T: FnMut() + 'a>(v: &mut Vec<Box<FnMut() + 'a>>, f: T) {
    v.push(Box::new(f));
}

fn call_b() {
    println!("Call b.");
}

#[test]
fn it_works() {
    let mut calls: Vec<Box<FnMut()>> = Vec::new();

    add_to_vec(&mut calls, || { println!("Call a."); });
    add_to_vec(&mut calls, call_b);

    for mut c in calls.drain() {
        c();
    }
}

The lifetime issue is that the boxed function objects must have a common base lifetime; if you just write the generic constraint T: FnMut(), it is assumed to only need to live as long as the function call and not any longer. Therefore two things need to be added to it all: the generic parameter T must be constrained to a specified lifetime, and in order to store it inside the vector, the trait object type must similarly be constrained, as Box<FnMut() + 'a>. That way they both match up and memory safety is ensured and so the compiler lets it through. The -> () part of FnMut() -> () is superfluous, by the way.

The remaining fixes that need to be made are the insertion of a few mut; in order to push to the vector, you naturally need a mutable reference, hence the & to &mut changes, and in order to take mutable references to calls and c the bindings must be made mut.

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • Why, in this code, if I change `Box::new(f)` to `Box::::new(f)` does it fail to compile? Am I not just specifying the type parameters fully instead of letting Rust infer them? – Thanatos Mar 13 '15 at 03:22
  • Also, in your example, in `add_to_vec`, why must we fully specify out `Box`, and why is that not equivalent to just `T`? – Thanatos Mar 13 '15 at 03:27
  • @Thanatos: you mean in the vector, how it is `&mut Vec>` rather than `Vec`? Think about it and it should become apparent: what is the type of the vector supposed to be? – Chris Morgan Mar 13 '15 at 03:31
  • Yes, the vector, but rather than `Vec>`; since `T` is `FnMut() + 'a`, `Vec` would be `Vec` which lacks our `Box`, which is clearly different; I'm asking how `&mut Vec>` different from `&mut Vec>`? – Thanatos Mar 13 '15 at 04:24
  • 1
    `T` is *not* `FnMut() + 'a`; `T` is a concrete type that *implements* `FnMut()` and lives for at least `'a`. Remember that when dealing with functions and closures, *each is of a unique type*. Taking just `call_b`, it has a type which is in friendly form (for compiler errors) written `fn() {call_b}`, `T` is `fn() {call_b}`, and so `Vec>` would be `Vec>`. – Chris Morgan Mar 13 '15 at 05:28