Method chain compiles; equivalent local closure does not

I've trimmed this down to the simplest version I possibly could (notably, everything goes fine when there's only one nested level of step).

I present two different ways of extracting elements of an iterator to feed them into a struct, or returning an error if the iterator runs out of elements: the first explicitly chains .next() and .map_or() on an iterator; the second uses a local closure that does exactly the same, but hinds it behind a name.

#[derive(Debug)]
pub struct MyArgs {
    first: String,
    second: String,
}

impl MyArgs {
    fn new(mut args: std::env::Args) -> Result<MyArgs, &'static str> {
        args.next();
        args.next().map_or(Err("missing first"), |first| {
            args.next()
                .map_or(Err("missing second"), |second| Ok(MyArgs { first, second }))
        })
    }

    // fn new2(mut args: std::env::Args) -> Result<MyArgs, &'static str> {
    //     let step = |e, f| args.next().map_or(e, f);
    //     step(Err("missing 0th"), |_| {
    //         step(Err("missing first"), |first| {
    //             step(Err("missing second"), |second| Ok(MyArgs { first, second }))
    //         })
    //     })
    // }
}

fn main() {
    println!("{:?}", MyArgs::new(std::env::args()));
    // println!("{:?}", MyArgs::new2(std::env::args()));
}

If you compile rustc args_next.rs and run ./args_next ... with varying arguments, you'll see the expected behavior. However, if you uncomment new2 and compile, you should get the following error:

error[E0308]: mismatched types
  --> args_next.rs:19:40
   |
19 |               step(Err("missing first"), |first| {
   |  ________________________________________^
20 | |                 step(Err("missing second"), |second| Ok(MyArgs { first, second }))
21 | |             })
   | |_____________^ expected closure, found a different closure
   |
   = note: expected closure `[closure@args_next.rs:20:45: 20:82 first:_]`
              found closure `[closure@args_next.rs:19:40: 21:14 step:_]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object

error[E0308]: mismatched types
  --> args_next.rs:18:34
   |
18 |           step(Err("missing 0th"), |_| {
   |  __________________________________^
19 | |             step(Err("missing first"), |first| {
20 | |                 step(Err("missing second"), |second| Ok(MyArgs { first, second }))
21 | |             })
22 | |         })
   | |_________^ expected closure, found a different closure
   |
   = note: expected closure `[closure@args_next.rs:20:45: 20:82 first:_]`
              found closure `[closure@args_next.rs:18:34: 22:10 step:_]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.

I can't manage to reason through the issue here, as I can eyeball this and tell they are doing exactly the same thing—I suspect it's an issue with the inferred types, but I can't fix it.

Note that changing the outer step call to a simple args.next(); step(Err("missing first"), ...) still results in the compiler error. As I said, nesting any deeper than a single call to step gives this error.

Interestingly, changing to

    fn new2(mut args: std::env::Args) -> Result<MyArgs, &'static str> {
        let step = |e, f| args.next().map_or(e, f);
        step(Err("missing 0th"), |_| {
            args.next().map_or(Err("missing first"), |first| {
                args.next()
                    .map_or(Err("missing second"), |second| Ok(MyArgs { first, second }))
            })
        })
    }

gives a different, perhaps more revelatory, message?

error[E0596]: cannot borrow `step` as mutable, as it is not declared as mutable
  --> args_next.rs:18:9
   |
17 |         let step = |e, f| args.next().map_or(e, f);
   |             ---- help: consider changing this to be mutable: `mut step`
18 |         step(Err("missing 0th"), |_| {
   |         ^^^^ cannot borrow as mutable

error[E0499]: cannot borrow `args` as mutable more than once at a time
  --> args_next.rs:18:34
   |
17 |         let step = |e, f| args.next().map_or(e, f);
   |                    ------ ---- first borrow occurs due to use of `args` in closure
   |                    |
   |                    first mutable borrow occurs here
18 |         step(Err("missing 0th"), |_| {
   |         ----                     ^^^ second mutable borrow occurs here
   |         |
   |         first borrow later used by call
19 |             args.next().map_or(Err("missing first"), |first| {
   |             ---- second borrow occurs due to use of `args` in closure

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0499, E0596.
For more information about an error, try `rustc --explain E0499`.

The problem is that the |first| ... closure and |second| ... closure have distinct types, so there is no single type for the f parameter in step.

Your second example reveals another problem with this approach: step needs &mut args to function, so recursively calling itself will violate aliasing rules unless you turn that into a shared reference with something like RefCell.

The closest thing I could make work is this: (Playground)

fn new2(args: Args) -> Result<(), &'static str> {
    fn step<Out>(mut args: Args, e: Out, f: impl FnOnce(String, Args) -> Out) -> Out {
        let arg = args.next();
        arg.map_or(e, |x| f(x, args))
    }

    step(args, Err("missing 0th"), |_, tail| {
        step(tail, Err("missing first"), |first, tail| {
            step(tail, Err("missing second"), |second, _| Ok(()))
        })
    })
}

If it were my project, I’d probably do something like this instead: (Playground)

fn new2b(mut args: Args) -> Result<(), &'static str> {
    match (args.next(), args.next(), args.next()) {
        (_, Some(first), Some(second)) => Ok(()),
        (None, _, _) => Err("0 missing"),
        (_, None, _) => Err("1 missing"),
        (_, _, None) => Err("2 missing")
    }
}

Can you elaborate on the different types? I would expect both f to be something like Fn(String) -> Result<(), &'static str>. I guess I see the point about recursive calls, maybe, but it's still not clear to me that they are doing anything inherently different.

Fn(String)->Result<(), &’static str> is a trait that’s implemented by all closures that have that call signature. The underlying type is something like a struct that contains a function pointer and one field for each value the closure captures.

The |first| {} closure contains only a reference to step, but the |second| {} closure contains both &mut step and a copy of first. Because they contain different fields, they must be different types— every (non-closure) function that accepts a closure argument does so via a type parameter or dynamic dispatch, though it may be hidden behind impl Fn() like in my example.

Closures themselves can’t be generic as far as I’m aware: their parameter can only be of one specific type, which is determined by the type inference system.


I suspect that you’d see the same error if you tried to extend your first implementation to three or more arguments, but it’s possible the compiler would be able to figure it out: each closure is completely finished with args before it creates its inner closure.

With step, you have to create the inner closure first, so that it can be passed as a parameter to step. Because both step and the inner closure need to hold &mut args, you get a double-borrow error.

Important nitpick: The type for a closure does not contain the function pointer! This also means that closures that don’t capture any variables are zero-sized. (Plus there’s extra magic that allows to turn non-capturing closures into fn pointers.)

Instead, every single closure expression has a new distinct type and that type implements all Fn* traits that it possibly can. The closure’s code is only in these trait implementations.

You can also get a “struct that combines function pointer and data” version of a closure when you go dynamic, that is, when you are working with trait objects. There the function pointer is in the vtable of the Fn trait.

1 Like

So @steffahn are you saying there is no non-macro definition of step that could make this work because the closure arguments cannot have the same types? I thought maybe trying to constrain the argument to the Fn trait I gave would work, but either I did it wrong or it didn’t work because I couldn’t make it compile.

Well, as long as closures can’t be generic (in case you didn’t know, they currently can’t be generic), there’s no direct way, but one could try to go into trait objects...

I actually tried this and the following still doesn’t compile

    fn new2(mut args: std::env::Args) -> Result<MyArgs, &'static str> {
        let mut step = |e, f: Box<dyn FnOnce(String) -> Result<MyArgs, &'static str>>| args.next().map_or(e, f);
        step(Err("missing 0th"), Box::new(|_| -> _ {
            step(Err("missing first"), Box::new(|first| -> _ {
                step(Err("missing second"), Box::new(|second| -> _ {Ok(MyArgs { first, second })}))
            }))
        }))
    }

The error then is

error[E0499]: cannot borrow `step` as mutable more than once at a time
  --> src/main.rs:18:43
   |
18 |         step(Err("missing 0th"), Box::new(|_| -> _ {
   |         ----                              ^^^^^^^^ second mutable borrow occurs here
   |         |
   |         first mutable borrow occurs here
   |         first borrow later used by call
19 |             step(Err("missing first"), Box::new(|first| -> _ {
   |             ---- second borrow occurs due to use of `step` in closure

for which I haven’t found any straightforward fix other than the passing the Args around that @2e71828 did. But then there’d be little gain in keeping step a closure.


Btw, ignore the extra -> _ things, they remained from earlier attempts of different stuff.

I’m not at a computer to test, but I think something like this could be made to work:

fn new2(args: std::env::Args) -> Result<MyArgs, &'static str> {
        let args = RefCell::new(args);
        let mut step = |e, f: Box<dyn FnOnce(String) -> Result<MyArgs, &'static str>>| {
            { args.borrow_mut().next() }.map_or(e, f)
        };
        step(Err("missing 0th"), Box::new(|_| -> _ {
            step(Err("missing first"), Box::new(|first| -> _ {
                step(Err("missing second"), Box::new(|second| -> _ {Ok(MyArgs { first, second })}))
            }))
        }))
    }

Without thinking about why, here’s the result of your suggestion of using RefCell:

thread 'main' panicked at 'already borrowed: BorrowMutError'

(but, hey, at least it compiles!)

Changing it to

            let next = args.borrow_mut().next();
            next.map_or(e, f)

works


btw, only tested in the playground for the no arguments (well, that means 1 argument) case though.

1 Like

Hmm, I had hoped the extra scope around borrow_mut().next() would force the guard to drop before f is called.

Interestingly, { let n = args.borrow_mut().next(); n }.map_or(...) works, too.


Edit: so it seems like temporaries in the “return expression” of a block are kept for the entire containing expression. (Also works transitively, when you nest multiple blocks.)

I might file a bug report tomorrow about that; it feels like

{ expr }

and

{ let x = expr; x }

should be equivalent.

Btw, in the meantime, (||args.borrow_mut().next())().map_or(e, f) is another alternative that works.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.