How I can implement FnOnce on Rust stable?

struct Closure<'a> {
    s : String,
    t : &'a String,
}

impl<'a> FnOnce<()> for Closure<'a> {
    type Output = String;
    fn call_once(self, args : ()) -> Self::Output {
        self.s += &*self.t;
        self.s
    }
}

This is one of the examples that I saw on internet and all of them didn't work for me.
I also try to fix lat rustc suggest to fix but still no success. I just give up and decide to ask how I can do that on rust stable.
Also is it possible to capture mutable reference within closure that is used as fnonce ?

Doing this requires the feature fn_traits, which is unstable. Thus, you need to use nightly, and not stable. What are you trying to do, exactly?

The std::ops::FnOnce trait is unstable (the design isn't fully finalised) and you can only implement it with a nightly compiler and the unboxed_closures feature flag.

The compiler's error message explains in more detail and also links to a ticket on GitHub:

error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change
 --> src/lib.rs:8:10
  |
8 | impl<'a> FnOnce<()> for Closure<'a> {
  |          ^^^^^^^^^^ help: use parenthetical notation instead: `FnOnce() -> ()`
  |
  = note: see issue #29625 <https://github.com/rust-lang/rust/issues/29625> for more information
  = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable

(playground)

Yes, normal closures can take a mutable reference to a captured variable.

fn main() {
    let mut i = 0;
    
    let mut closure = || {
        let mutable_i: &mut u32 = &mut i;
        
        *mutable_i += 1;
    };
    
    closure();
    closure();
    closure();
    // drop the closure so `i` is no longer borrowed mutable
    drop(closure);
    // and now we can print the result
    println!("i: {}", i);
}

(playground)

Try this out (compile on nightly!):

#![feature(unboxed_closures,fn_traits)]
struct Closure<'a> {
    s : String,
    t : &'a String,
}

impl<'a> FnOnce<()> for Closure<'a> {
    type Output = String;
    extern "rust-call" fn call_once(mut self, _args : ()) -> Self::Output {
        self.s += &*self.t;
        self.s
    }
}

impl<'a> FnMut<()> for Closure<'a> {
    extern "rust-call" fn call_mut(&mut self, _args : ()) -> Self::Output {
        self.s += &*self.t;
        // self is taken by mutable reference, so we need to clone it to return it
        self.s.clone()
    }
}

fn take_closure(value: impl FnOnce() -> String) {
    println!("Value is: {}", value());
}

fn main(){
    let val = String::from("world!");
    let closure = Closure { s: String::from("hello "), t: &val };
    take_closure(closure);
}
1 Like

thanks but I have few problems.

  1. it look like it is unstable feature and I should enable it manually
  2. it allow me to make function that take only 1 argument except self
  3. I can build it in the playground but I can't build it on my machine probably I should enable something
error[E0658]: rust-call ABI is subject to change
  --> src/abstraction/piston_abstraction.rs:92:12
   |
92 |     extern "rust-call" fn call_once(mut self, _args : ()) -> Self::Output {
   |            ^^^^^^^^^^^
   |
   = note: see issue #29625 <https://github.com/rust-lang/rust/issues/29625> for more information

error[E0658]: rust-call ABI is subject to change
  --> src/abstraction/piston_abstraction.rs:99:12
   |
99 |     extern "rust-call" fn call_mut(&mut self, _args : ()) -> Self::Output {
   |            ^^^^^^^^^^^
   |
   = note: see issue #29625 <https://github.com/rust-lang/rust/issues/29625> for more information

I exercise some generics with rust and for the purpose decide abstract my tetris game from piston (game framework)

    fn loop_handler(&mut self, game : &StateMachine, loop_arg: Loop, event: Event) -> bool {
        match loop_arg {
            Loop::Update(update_args) => {
                // if !self.update(update_args, event.clone()) {
                //     return true;
                // }
            }

            Loop::Render(render_args) => {
                self.window.draw_2d(&event, |c, g, device| {
                    clear([1.0; 4], g);
                    let ctx = PistonRenderContext::new(c, g, &render_args, device);
                    // game.render(&ctx);

                });
            }

            _ => {}
        }

        false
    }

And I can't capture game as reference because window.draw_2d take closure of type fnonce. I didn't expect that it is not possible to implement that in rust

Arguments are passed as a tuple, so they go between the ():

impl FnOnce<(String, i32)> for Closure<'a> {
    extern "rust-call" fn call_once(mut self, (a_string, an_int): (String, i32)) -> Self::Output {
        // do stuff with a_string and an_int
2 Likes

I suspect you're focusing on the wrong part of the error message -- FnOnce is not something you should normally need to implement manually. Can you share the full error?

this is the error that I get

error[E0507]: cannot move out of `*game` which is behind a shared reference
   --> src/abstraction/piston_abstraction.rs:135:21
    |
135 |                     game.render(&ctx);
    |                     ^^^^ move occurs because `*game` has type `states::state_machine::StateMachine`, which does not implement the `Copy` trait

and here is the code that cause it

    fn loop_handler(&mut self, game : &StateMachine, loop_arg: Loop, event: Event) -> bool {
        match loop_arg {
            Loop::Update(update_args) => {
                // if !self.update(update_args, event.clone()) {
                //     return true;
                // }
            }

            Loop::Render(render_args) => {
                self.window.draw_2d(&event, |c, g, device| {
                    clear([1.0; 4], g);
                    let ctx = PistonRenderContext::new(c, g, &render_args, device);
                    game.render(&ctx);

                });
            }

            _ => {}
        }

        false
    }

I believe that this can never work because fnonce try to capture by value

The problem is that render takes self instead of &self I think.

3 Likes

thanks