ruststaticclosures

how to define a global closure/functor and pass to a function that determine if it's the predefined in Rust?


I want to predefine several functions/closes. They could be used to pass to a function which determines if it's predefined ones.

// static? const? or global fn()?
static prefined1: Fn()+'static = || { do_some_thing(); }
static prefined2: Fn()+'static = || { do_other_thing();}

fn call_func(f: impl Fn()+'static) {
    if f == predefined1 {  // how to compare closures???
         // do some thing with predefined1 
    } else if f == predefined2 {
         // do some thing with predefined12
    } else {
         // others.
    }
}

Solution

  • Functions cannot be compared directly, but you can compare their pointers (which contain an address and, in the case of dyn, a vtable pointer):

    static PREDEFINED_1: fn() = || { println!("foo"); };
    static PREDEFINED_2: fn() = || { println!("bar"); };
    
    fn call_func(f: &dyn Fn()) {
        if std::ptr::eq(f, &PREDEFINED_1) {
             println!("found 1");
        } else if std::ptr::eq(f, &PREDEFINED_2)  {
             println!("found 2");
        } else {
             println!("found neither");
        }
    }
    
    pub fn main() {
        call_func(&PREDEFINED_1);
        call_func(&PREDEFINED_2);
        call_func(&|| { println!("foo") });
    }
    

    In general, this is a big mistake, because the compiler may collapse identical functions into the same address or emit multiple copies of the vtable (such as due to inlining and codegen units). These could cause false positives and false negatives, respectively.

    For these reasons, you should probably use an enum instead:

    enum Func {
        Predefined1,
        Predefined2,
        Custom(Box<dyn Fn()>)
    }
    
    fn call_func(f: Func) {
        match f {
            Func::Predefined1 => println!("found 1"),
            Func::Predefined2 => println!("found 2"),
            Func::Custom(_c) => println!("found neither"),
        }
    }
    
    pub fn main() {
        call_func(Func::Predefined1);
        call_func(Func::Predefined2);
        call_func(Func::Custom(Box::new(|| { println!("foo"); })));
    }