Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

the trait `std::ops::Fn<()>` is not implemented for [closure ...] but only with a binding from the outer scope

Tags:

rust

fn main() {

    let s = Some("xyz".to_string());  //compiler error

    let foo = Box::new(|| s) as Box<Fn() -> Option<String>>;  //ok

    let bar = Box::new(|| Some("xyz".to_string())) as Box<Fn() -> Option<String>>;

    println!("{:?}", foo());
    println!("{:?}", bar());
}

gives the error

error[E0277]: the trait bound `[closure@src/main.rs:5:24: 5:28 s:std::option::Option<std::string::String>]: std::ops::Fn<()>` is not satisfied
 --> src/main.rs:5:15
  |
5 |     let foo = Box::new(|| s) as Box<Fn() -> Option<String>>;
  |               ^^^^^^^^^^^^^^ the trait `std::ops::Fn<()>` is not implemented for `[closure@src/main.rs:5:24: 5:28 s:std::option::Option<std::string::String>]`
  |
  = note: required for the cast to the object type `std::ops::Fn() -> std::option::Option<std::string::String>`

error: aborting due to previous error

The docs for Trait std::ops::Fn state:

Fn is implemented automatically by closures which only take immutable references to captured variables or don't capture anything at all,

s isn't mutable, but it's not a reference and I'm moving it.

If I call s.clone() the compiler error goes away, but in my real case I want to avoid that.

Same error if I use FnMut FnOnce complains about not knowing the size, even though it is Boxed.

Historical note: FnOnce used to give an error, but it works starting with Rust 1.35.

Is there a way I can make this work with moved data?

playground

like image 349
marathon Avatar asked Oct 21 '25 04:10

marathon


1 Answers

If this was allowed, what would happen the second time the closure was called? Remember, the first time the closure is called, it moves s, so s now has no valid value.

There are a few ways to make this work, depending on what you need exactly.

  1. Make the closure return a reference to the string instead.

    Note: We need to write out 'a explicitly on the right-hand side of the as cast expression, otherwise the compiler gives an error. I don't think we can write a correct lifetime without introducing an intermediate function (make_foo here).

    fn make_foo<'a>(s: &'a Option<String>) -> Box<dyn Fn() -> Option<&'a str> + 'a> {
        Box::new(move || s.as_ref().map(|s| &**s)) as Box<dyn Fn() -> Option<&'a str> + 'a>
    }
    
    fn main() {
        let s = Some("xyz".to_string());
        let foo = make_foo(&s);
    
        println!("{:?}", foo());
    }
    
  2. Use FnOnce instead of Fn. FnOnce closures can move, but can be called at most once.

    fn main() {
        let s = Some("xyz".to_string());
        let foo = Box::new(|| s) as Box<dyn FnOnce() -> Option<String>>;
    
        println!("{:?}", foo());
    }
    
like image 179
Francis Gagné Avatar answered Oct 24 '25 19:10

Francis Gagné



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!