Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"error: reached the recursion limit during monomorphization" with unboxed closures #19596

Closed
japaric opened this issue Dec 6, 2014 · 8 comments
Labels
A-closures Area: Closures (`|…| { … }`)

Comments

@japaric
Copy link
Member

japaric commented Dec 6, 2014

Came up with this issue while "unboxing" the collections::trie::map::InternalNode::each_reverse method.

Reduced test case

#![feature(unboxed_closures)]

struct Struct;

impl Struct {
    fn method_ok(&self, f: |()| -> bool) -> bool {
        self.method_ok(|t| f(t))
    }

    fn method_bad<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
        self.method_bad(|t| f(t))
    }

    fn method_bad2<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
        let f: &Fn(()) -> bool = &f;

        self.method_bad2(|t| f.call((t,)))
    }
}

fn main() {
    Struct.method_ok(|_| true);
    // Pick one
    Struct.method_bad(|_| true);
    //Struct.method_bad2(|_| true);
}

Output

limit.rs:10:5: 12:6 error: reached the recursion limit during monomorphization
limit.rs:10     fn method_bad<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
limit.rs:11         self.method_bad(|t| f(t))
limit.rs:12     }

Version

rustc 0.13.0-dev (d9c7c00b9 2014-12-04 21:33:07 +0000)

cc @nikomatsakis (you already know, but I want to keep track of the issue)

@gereeter
Copy link
Contributor

gereeter commented Dec 9, 2014

This seems like expected behavior to me. Passing a closure to either of the method_bads will cause a specialized version of that method_bad to be created, including a new type for the wrapper unboxed closure. This is then passed to method_bad, creating a new specialized version and a second new type for the wrapper closure. The cycle repeats indefinitely.

The only only way that I could see rustc accepting this would be if it were to recognize the |x| f(x) idiom and replace it with f. This seems very distateful, though I would be happy to see a lint recommending it.

This should work as a replacement:

fn method_ok_unboxed<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
    self.method_ok_unboxed(f);
}

Additionally, it should be faster, as it won't be going through the useless indirection of |x| f(x).

@japaric
Copy link
Member Author

japaric commented Dec 9, 2014

@gereeter Sure, that works in the reduced case, but in the TrieMap code, it won't work because the method call is inside a for loop, and the operation you are suggesting moves the closure.

@gereeter
Copy link
Contributor

@japaric Why not have it just take the closure by reference, then?

@japaric
Copy link
Member Author

japaric commented Dec 10, 2014

Changing the signature to always take the closure by reference is a loss in ergonomics:

trie_map.each_reverse(&mut |a, b| /* body */);
// vs
trie_map.each_reverse(|a, b| /* body */);

What we probably want to do is add the following impl:

impl<'a, Args, Result> FnMut<Args, Result> for &'a mut F where F: FnMut<Args, Result> { /* */ }

That way we can pass the closure either by value or by reference. However, due to #18835/#19032 we can't do that at the moment.

bors added a commit that referenced this issue Jan 1, 2015
The the last argument of the `ItemDecorator::expand` method has changed to `Box<FnMut>`. Syntax extensions will break.

[breaking-change]

---

This PR removes pretty much all the remaining uses of boxed closures from the libraries. There are still boxed closures under the `test` directory, but I think those should be removed or replaced with unboxed closures at the same time we remove boxed closures from the language.

In a few places I had to do some contortions (see the first commit for an example) to work around issue #19596. I have marked those workarounds with FIXMEs. In the future when `&mut F where F: FnMut` implements the `FnMut` trait, we should be able to remove those workarounds. I've take care to avoid placing the workaround functions in the public API.

Since `let f = || {}` always gets type checked as a boxed closure, I have explictly annotated those closures (with e.g. `|&:| {}`) to force the compiler to type check them as unboxed closures.

Instead of removing the type aliases (like `GetCrateDataCb`), I could have replaced them with newtypes. But this seemed like overcomplicating things for little to no gain.

I think we should be able to remove the boxed closures from the languge after this PR lands. (I'm being optimistic here)

r? @alexcrichton or @aturon 
cc @nikomatsakis
bors added a commit that referenced this issue Jan 5, 2015
This PR removes boxed closures from the language, the closure type syntax (`let f: |int| -> bool = /* ... */`) has been obsoleted. Move all your uses of closures to the new unboxed closure system (i.e. `Fn*` traits).

[breaking-change] patterns

- `lef f = || {}`

This binding used to type check to a boxed closure. Now that boxed closures are gone, you need to annotate the "kind" of the unboxed closure, i.e. you need pick one of these: `|&:| {}`, `|&mut:| {}` or `|:| {}`.

In the (near) future we'll have closure "kind" inference, so the compiler will infer which `Fn*` trait to use based on how the closure is used. Once this inference machinery is in place, we'll be able to remove the kind annotation from most closures.

- `type Alias<'a> = |int|:'a -> bool`

Use a trait object: `type Alias<'a> = Box<FnMut(int) -> bool + 'a>`. Use the `Fn*` trait that makes sense for your use case.

- `fn foo(&self, f: |uint| -> bool)`

In this case you can use either a trait object or an unboxed closure:

``` rust
fn foo(&self, f: F) where F: FnMut(uint) -> bool;
// or
fn foo(&self, f: Box<FnMut(uint) -> bool>);
```

- `struct Struct<'a> { f: |uint|:'a -> bool }`

Again, you can use either a trait object or an unboxed closure:

``` rust
struct Struct<F> where F: FnMut(uint) -> bool { f: F }
// or
struct Struct<'a> { f: Box<FnMut(uint) -> bool + 'a> }
```

- Using `|x, y| f(x, y)` for closure "borrows"

This comes up in recursive functions, consider the following (contrived) example:

``` rust
fn foo(x: uint, f: |uint| -> bool) -> bool {
    //foo(x / 2, f) && f(x)  // can't use this because `f` gets moved away in the `foo` call
    foo(x / 2, |x| f(x)) && f(x)  // instead "borrow" `f` in the `foo` call
}
```

If you attempt to do the same with unboxed closures you'll hit ""error: reached the recursion limit during monomorphization" (see #19596):

``` rust
fn foo<F>(x: uint, mut f: F) -> bool where F: FnMut(uint) -> bool {
    foo(x / 2, |x| f(x)) && f(x)
    //~^ error: reached the recursion limit during monomorphization
}
```

Instead you *should* be able to write this:

``` rust
fn foo<F>(x: uint, mut f: F) -> bool where F: FnMut(uint) -> bool {
    foo(x / 2, &mut f) && f(x)
    //~^ error: the trait `FnMut` is not implemented for the type `&mut F`
}
```

But as you see above `&mut F` doesn't implement the `FnMut` trait. `&mut F` *should* implement the `FnMut` and the above code *should* work, but due to a bug (see #18835) it doesn't (for now).

You can work around the issue by rewriting the function to take `&mut F` instead of `F`:

``` rust
fn foo<F>(x: uint, f: &mut F) -> bool where F: FnMut(uint) -> bool {
    foo(x / 2, f) && (*f)(x)
}
```

This finally works! However writing `foo(0, &mut |x| x == 0)` is unergonomic. So you can use a private helper function to avoid this:

``` rust
// public API function
pub fn foo<F>(x: uint, mut f: F) -> bool where F: FnMut(uint) -> bool {
    foo_(x, &mut f)
}

// private helper function
fn foo_<F>(x: uint, f: &mut F) -> bool where F: FnMut(uint) -> bool {
    foo_(x / 2, f) && (*f)(x)
}
```

Closes #14798

---

There is more cleanup to do: like renaming functions/types from `unboxed_closure` to just `closure`, removing more dead code, simplify functions which now have unused arguments, update the documentation, etc. But that can be done in another PR.

r? @nikomatsakis @aturon (You probably want to focus on the deleted/modified tests.)
cc @eddyb
@huonw huonw added the A-closures Area: Closures (`|…| { … }`) label Jan 12, 2015
@huonw
Copy link
Member

huonw commented Jan 12, 2015

This seems like expected behavior to me.

I agree, but I think this is so subtle and hard to debug (the infinite chain of unique types is unexpected, since they all look the same in the source) that this is at least worth special error messages.

@SecondNature
Copy link

The compiler never returns when compiling this code. If you remove the predicate closure then you get "error: reached the recursion limit during monomorphization". But with both closures included it just loops and never returns. I would assume that the "recursion limit" test in the error message is failing with two closures.

rustc 1.0.0-nightly (3d0d9bb 2015-01-12 22:56:20 +0000)

#![crate_name = "compiler_bug"]

#![crate_type = "bin"]

#![allow(unused_variables)]

fn main() {
recursive(|| { false } , || { })
}

pub fn recursive< P: Fn() -> bool, F: Fn()>(predicate : P , function : F ) {
recursive( ||predicate(), ||function() );
}

I read that the error message may be expected behaviour. But this endless loop behaviour may influence your design choice or encourage a error to be added.

Can someone private message me a version of the included code that will compile.

@steveklabnik
Copy link
Member

We now have a better message. Updated code:

#![feature(unboxed_closures)]
#![feature(core)]

struct Struct;

impl Struct {
    fn method_bad<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
        self.method_bad(|t| f(t))
    }

    fn method_bad2<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
        let f: &Fn(()) -> bool = &f;

        self.method_bad2(|t| f.call((t,)))
    }
}

fn main() {
    // Pick one
    Struct.method_bad(|_| true);
    //Struct.method_bad2(|_| true);
}

errors:

hello.rs:11:5: 15:6 warning: method is never used: `method_bad2`, #[warn(dead_code)] on by default
hello.rs:11     fn method_bad2<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
hello.rs:12         let f: &Fn(()) -> bool = &f;
hello.rs:13 
hello.rs:14         self.method_bad2(|t| f.call((t,)))
hello.rs:15     }
hello.rs:7:5: 9:6 warning: function cannot return without recurring, #[warn(unconditional_recursion)] on by default
hello.rs:7     fn method_bad<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
hello.rs:8         self.method_bad(|t| f(t))
hello.rs:9     }
hello.rs:8:9: 8:34 note: recursive call site
hello.rs:8         self.method_bad(|t| f(t))
                   ^~~~~~~~~~~~~~~~~~~~~~~~~
hello.rs:7:5: 9:6 help: a `loop` may express intention better if this is on purpose
hello.rs:11:5: 15:6 warning: function cannot return without recurring, #[warn(unconditional_recursion)] on by default
hello.rs:11     fn method_bad2<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
hello.rs:12         let f: &Fn(()) -> bool = &f;
hello.rs:13 
hello.rs:14         self.method_bad2(|t| f.call((t,)))
hello.rs:15     }
hello.rs:14:9: 14:43 note: recursive call site
hello.rs:14         self.method_bad2(|t| f.call((t,)))
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
hello.rs:11:5: 15:6 help: a `loop` may express intention better if this is on purpose
hello.rs:7:5: 9:6 error: reached the recursion limit during monomorphization
hello.rs:7     fn method_bad<F>(&self, f: F) -> bool where F: Fn(()) -> bool {
hello.rs:8         self.method_bad(|t| f(t))
hello.rs:9     }

@oli-obk
Copy link
Contributor

oli-obk commented May 19, 2016

this doesn't look fixed to me, there's still various FIXME(#19596) in the source with workarounds, and fixing them still gives the following error message:

error: overflow evaluating the requirement `[closure@src/librustc/hir/pat_util.rs:177:14: 184:6 dm:&'static &'static std::collections::HashMap<u32, hir::def::PathResolution, core::hash::BuildHasherDefault<rustc_data_structures::fnv::FnvHasher>>, contains_bindings:&'static mut bool]: core::ops::FnMut<(&'static hir::Pat,)>` [E0275]
src/librustc/lib.rs:1:1: 1:1 note: consider adding a `#![recursion_limit="128"]` attribute to your crate
error: aborting due to previous error

fee1-dead added a commit to fee1-dead-contrib/rust that referenced this issue Feb 19, 2023
fee1-dead added a commit to fee1-dead-contrib/rust that referenced this issue Feb 20, 2023
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Feb 20, 2023
…=cjgillot

Remove old FIXMEs referring to rust-lang#19596

Having an inner function that accepts a mutable reference seems to be the only way this can be expressed. Taking a mutable reference would call the same function with a new type &mut F which then causes the infinite recursion error in rust-lang#19596.
bors added a commit to rust-lang-ci/rust that referenced this issue Feb 20, 2023
…iaskrgr

Rollup of 6 pull requests

Successful merges:

 - rust-lang#108241 (Fix handling of reexported macro in doc hidden items)
 - rust-lang#108254 (Refine error span for trait error into borrowed expression)
 - rust-lang#108255 (Remove old FIXMEs referring to rust-lang#19596)
 - rust-lang#108257 (Remove old FIXME that no longer applies)
 - rust-lang#108276 (small `opaque_type_origin` cleanup)
 - rust-lang#108279 (Use named arguments for `{,u}int_impls` macro)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-closures Area: Closures (`|…| { … }`)
Projects
None yet
Development

No branches or pull requests

6 participants