Why closure with move is not FnOnce

Guys, I cannot understand why the first example errors but the second example doesn't. Semantically they should be identical:

//First example
let args = Args::parse();

    MyServer::new(move || {
        let mut cfg = Config::new();
        cfg.host = Some(args.host);
}

error[E0507]: cannot move out of `args.host`, a captured variable in an `Fn` closure
  --> src/main.rs:37:25
   |
33 |     let args = Args::parse();
   |         ---- captured outer variable
34 |
35 |     MyServer::new(move || {
   |                     ------- captured by this `Fn` closure
36 |         let mut cfg = Config::new();
37 |         cfg.host = Some(args.host);
   |                         ^^^^^^^^^^^^ move occurs because `args.host` has type `std::string::String`, which does not implement the `Copy` trait

And this^^^ is "somewhat" logical to me.

//Second example


    MyServer::new(move || {
let args = Args::parse();//here I moved the args var inside the clousure
        let mut cfg = Config::new();
        cfg.host = Some(args.host);
}

This works, but to me this example is semantically identicall to the previous one. Here I manually moved the args variable inside the clousure where in the previous example I used move keyword to do that for me.
Can somebody explain what is happening here?
Thank you

Your post is lacking details, but here's my guess.

In the failing example, you call Args::parse() once outside the closure, and consume it post-parse by moving it into the closure. Then you move the field out within the closure, so the closure can only be a FnOnce. The args variable isn't complete after the move. There is only ever one args: Args created, outside of the closure.

In the succeeding example, you call Args::parse() every time the closure is called. There's a brand new args: Args created every time you call the closure. By the end of the function call, the args variable isn't complete due to the move, but that's ok -- you'll just create a new one the next time the closure gets called. So this closure can still implement Fn.

The key is calling Args::parse once and moving the result into the closure, versus calling Args::parse every time the closure is called (producing a fresh result).

Make sense?

2 Likes

Alternative explanation in code form.

How can you tell if it's Fn or FnOnce just by looking at the closure?

An Fn has reference access to its captures, so unless they are Copy, it can't move out of them. Only FnOnce has by-value access (which is why it's FnOnce).

This is a simplified example:

// Compile error
fn main() {
    let args = Args::default();
    foo(move || {
        let a = args.host;
        dbg!(a);
    });
}

#[derive(Default, Debug)]
struct Args {
    host: String,
}

fn foo<F>(f: F)
where
    F: Fn(),
{
    f();
}

In Fn doc:

/// Instances of Fn can be called repeatedly without mutating state.

In FnOnce doc:

/// Instances of FnOnce can be called, but might not be callable multiple times.

The error hint ask args.host to be Copy, let's try change host from String to i32 which is Copy, it certainly can be called repeatedly, because no data moved out from args.

But for String, no Copy trait, let a = args.host moved out host from args, so args is broken, and that field cannot be used again. So it's FnOnce.

Let's fix this:

fn foo<F>(f: F)
where
    F: FnOnce(), // FnOnce instead
{
    f();
}

It's not syntactical so you can't tell by looking unless you know the types involved.

There's more information here. However it is lacking one significant clarification: defining a closure directly in a context that expects certain Fn-trait bounds, usually a function argument position, will influence how the compiler implements the closure.[1]


  1. Here's an example where a closure could have been Fn, and is if you define it first and then pass it, but the context convinced the compiler to only implement FnOnce and FnMut, for example. ↩︎

2 Likes

Hi, no, not really. I mean, that keyword move moves the variable used in the closure into the closure, so we cannot use it outside of the closure anymore. If that's correct, why can't we:
a) Move out of this moved variable one of its fields? We are owners of this variable (the args var).
b) Why move doesn't mean the same as declaring that variable (the args var) inside the closure?

With "picture":

let args = get_args();

let my_closure = move || {
//here I moved the ownership of the args var inside the closure, so this closure now owns it.
//Why isn't that equivalent to declaring the args inside that closure? Semantically it is identicall.
let mut cfg = Config::new();
        cfg.host = Some(args.host);
}

So, in other words, why is closure with move not FnOnce?

Hi, sure, I understand the difference between Fn, FnMut and FnOnce. The point is that you didn't change the "calling" code, you've just change the requirement that the calling code must be FnOnce. And that's exactly is my "problem". Why isn't that FnOnce if I move'd the variable inside that closure?

I can ask similar questions with regards to forums? Why forums like this exist when everything is clearly explained in documentation?

people are spending so much trying to understand things

Exactly, sometimes not everything is as obvious to others as it is to you.

TBH, I don't think your point about partial move is correct here. But I'm more than happy to learn otherwise.

It is: proof.

Every callable (be it a closure, an fn item, or a function pointer) is at least FnOnce.

:slight_smile:
OK, so why then compiler complains in my OP and insists that that closure is Fn not FnOnce?

What's the signature of MyServer::new()? Does it expect an Fn rather than an FnOnce perchance?

The error message suggests that your closure is inferred to implement Fn (in addition to FnOnce). That's probably because you have some other, borrowing code that you are not showing us.

The thing is: implementing FnOnce does not preclude a closure from implementing Fn or FnMut – it merely gives callers a weaker precondition ("it can be called once") compared to Fn ("it can be called many times even if this causes overlapping borrows") or FnMut ("it can be called many times but only as long as it doesn't cause overlapping mutable borrows").

Implementing Fn or FnMut doesn't disallow implementing FnOnce, either. Again, FnOnce is always implemented by all things callable. In addition, Fn and FnMut is also implemented by anything that doesn't capture (fn items and fn pointers), because these unconditionally (and vacuously) satisfy the requirements on captures variables.

What's not possible is implementing Fn (capturing by reference) and then trying to move out of a capture.

Logically, trait bounds behave the opposite way when viewed from the side of the implementor vs. the PoV of the user:

  • A stronger trait bound (eg. Fn instead of FnOnce) is:
    • a stronger liability to the implementor, because it can't do anything that doesn't satisfy the stricter requirements of the trait (eg, can't move out of captures);
    • but it's more convenient to the consumer of the bounded type, because it can do more with it (eg., can call the function many times).

On the other hand, a weaker trait bound is:

  • more convenient to implementors, because they don't have to obey so many rules, eg. can move out of captures,
  • but less useful to consumers, because they can't rely on as many properties (eg., can only call once, in case the closure moved the captures).

If you have some requirement (eg., an Fn bound or a borrow) that causes your closure to be inferred as an Fn, then there's no way you can implement it by moving out of its captures. It always has to obey the rules of the strictest trait it implements, because someone may use it through that trait. The fact that someone else can also use it via FnOnce, for example, does not help with this, obviously.

4 Likes

So your question actually is:

Why after move parse() into closure, it is changed from FnOnce into Fn?

Here's some facts helpful:

  • If something can be use as many times as you like, you can certainly use it only once.
  • Fn means the closure can be used repeatedly. F: Fn() means F needs being able to used repeatedly.
  • FnOnce means the closure can be used only once. F: FnOnce() means F needs being able to be used at least once.

The 3 facts is easy, but it's at least TRUE.

So we can say:

  • if F satisfies Fn, it can be used like FnOnce
  • if F satisfies FnOnce, it may not be used as Fn

In other words:
if F: Fn(), then F: FnOnce()

Lets back to the question:

The fact is the closure is still FnOnce, but also Fn.

// This is able to compile
fn main() {
    let args = Args::default();
    foo(move || {
        let a = args.host;    // host<i32> is `Copy`, so this closure is `Fn`
        dbg!(a);
    });
}

#[derive(Default, Debug)]
struct Args {
    host: i32,
}

fn foo<F>(f: F)
where
    F: FnOnce(),    // This asks a `FnOnce`
{
    f();
}

Edit: The key point is, many would think that F: FnOnce() always means the closure F can be used only once, that's not true.

  • When define closure, if it can be used at least once, then it's FnOnce, if it can even be used repeatedly, then it's Fn.
  • When used in trait bound, always remember trait bound is used to constrain args. F: FnOnce() means, F needs being able to be used at least once. F: Fn() means, F needs being able to be used repeatedly.
1 Like

No, as a trait bound, it means "it can be used at most once". If you have a generic type variable F bounded by FnOnce, you'll only be able to call variables of that type once (or never) in a generic context.

FnOnce means "callable at least once" at the impl site.

1 Like

It is required to be Fn by the signature of the method you're passing it to. If you weren't doing that, then the compiler would comfortably infer FnOnce (only) and your code would compile (example).

As for why MyServer::new requires that its argument be at least Fn, you'd have to read the docs for that method to find out, but I would guess that it needs to be able to call it repeatedly. Given that constraint, then the captured args variable must be in a valid state by the end of the function; if you move args.host out of it, then it has been partially invalidated unless you move something else in.

I'd probably fix this by using args.host.clone() to avoid moving args.host, at the probably-inconsequential cost of copying that string every time this closure is run (example).

3 Likes

This is due to the inference behavior that @quinedot referenced. If you write something that looks more like

let f = move || { … };
Server::new(f)

then Rust will look at the closure first and infer that it is only FnOnce, then it will look at the call to Server::new and complain that the argument is FnOnce but Server::new requires it to be Fn.

If you instead write

Server::new(move || { … })

then Rust sees Server::new first, so it knows that the closure must be Fn. Then, when it checks the closure, it infers the closure to be Fn and complains that the body of the closure does something that isn't permitted in a repeatably callable closure.

Usually this results in better error messages; it's a question of where the "blame" is assigned. But in some contexts it can result in surprising behavior, especially if you aren't expecting what looks to be trivial source differences to change inference in this manner.


TL;DR: the compiler expects the closure to be Fn because you're telling it that you expect it to be Fn when you call MyServer::new. If you don't need it to be called multiple times, then the signature should be shaped like fn new(impl FnOnce() -> Config) -> Self instead of fn new(impl Fn() -> Config) -> Self[1].


  1. The signature of fn new<F: Fn() -> Config>(F) -> Self using a generic instead of impl Trait syntax is functionally equivalent here. ↩︎

6 Likes

PS: I modified that answer again. (I'm not a native speaker, so maybe I used some inaccurate expression)

Yes, certainly, FnOnce means the closure can be used only once.

But I still think, in a trait bound, F: FnOnce() means:
I need a F, and F can be used at least once.

For examle:

fn foo<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

Outside foo, foo wants a F can be used at least once. Inside foo, you can only use f once certainly.

Let's assume F: FnOnce() means foo wants a F which can only be used once. Then something below should not be compiled:

let f = | | {...} ;      // f is a closure, and is `Fn`, which can be used repeatedly
foo(f);

If F should be only used once, then f, which is Fn and can be used repeatedly, certainly cannot be passed to foo. The compiler should refuse it. But the fact is, Fn closure can be used as FnOnce.

Then, I have to think, F: FnOnce() where FnOnce is used as a constrain on F, means F needs being able to be used at least once.

Although this works in my metal model, there may still be something wrong in fact, please let me know.

1 Like

It's correct that FnOnce means "at least once" in this way. Rust trait bounds are additive capabilities, and there's no way to express a bound for types which can't do something, but only for types which can.

Since Rust types can always be dropped, a closure which can be called multiple times can trivially be called only once, simply by only calling it once and not calling it again.

This is one of those places where it's just difficult to express mathematical concepts linguistically in a precise manner.

2 Likes