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

RIP boxed closures #20578

Merged
merged 37 commits into from
Jan 6, 2015
Merged

RIP boxed closures #20578

merged 37 commits into from
Jan 6, 2015

Conversation

japaric
Copy link
Member

@japaric japaric commented 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:

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:

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:

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):

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:

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:

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:

// 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

@nikomatsakis
Copy link
Contributor

my hero.

@japaric
Copy link
Member Author

japaric commented Jan 5, 2015

I've updated the [breaking-change] message.

@alexcrichton
Copy link
Member

It may be worth noting that you can't actually get the old behavior of boxed closures today (#20575):

fn foo(f: &mut FnMut()) {
    f();
}
error: internal compiler error: unexpected panic
note: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: http://doc.rust-lang.org/complement-bugreport.html
note: run with `RUST_BACKTRACE=1` for a backtrace
thread 'rustc' panicked at 'assertion failed: index < LLVMCountParams(llfn)', /home/rustbuild/src/rust-buildbot/slave/nightly-dist-rustc-linux/build/src/librustc_llvm/lib.rs:2180

This does work, but requires the feature gate, however:

#![feature(unboxed_closures)]
fn foo(mut f: Box<FnMut()>) {
    f.call_mut(());          
}                            

@@ -4231,7 +4231,7 @@ arguments, really powerful things are possible.

Let's make a closure:

```{rust}
```{rust,ignore}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we un-ignore these for landing?

cc @steveklabnik

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, ignoring this is bad.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@japaric would you prefer a PR from me to your PR, or do you just want to change the syntax and I can land the language after?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be great if this landed with updated docs, so I'll go with the first option.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me tackle that for you right now then ❤️

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sent in japaric#1

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steveklabnik you need a stage1 compiler (built with this PR) to see the compiler errors in the doctests. Let me update the snippets and then you can update the text around. (That should be faster since I have a stage1 compiler at hand).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's... weird. Okay.

@japaric
Copy link
Member Author

japaric commented Jan 5, 2015

@alexcrichton Interestingly that ICE only occurs for trait objects with zero arguments, these two work without the feature gate:

fn foo(mut f: Box<FnMut(int)>) {
    f(5)
}

fn bar(mut f: &mut FnMut(int)) {
    f(5)
}

@alexcrichton
Copy link
Member

@japaric whoa nice! I had no idea! Pretend I was never here...

@jroesch
Copy link
Member

jroesch commented Jan 5, 2015

👍 👍

@nikomatsakis
Copy link
Contributor

@alexcrichton I fixed the ICE altogether and sent @japaric the commit. Hopefully it'll show up here at some point.

@alexcrichton
Copy link
Member

swoon

@japaric japaric force-pushed the no-more-bc branch 2 times, most recently from 80febed to 8c195be Compare January 5, 2015 21:07
@japaric
Copy link
Member Author

japaric commented Jan 5, 2015

It's ready! ICEs have been fixed (by @nikomatsakis), unignored and fixed the doctests in the guide and the reference. Last 6 commits are new.

bors added a commit that referenced this pull request 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
@bors bors merged commit eb2506c into rust-lang:master Jan 6, 2015
@jethrogb
Copy link
Contributor

jethrogb commented Jan 7, 2015

Note the example in the first PR comment is wrong:

fn foo(&self, f: F) where F: FnMut(uint) -> bool;

You'll get the error use of undeclared type name F``, use instead:

fn foo<F>(&self, f: F) where F: FnMut(uint) -> bool;

alkor added a commit to alkor/rust-http that referenced this pull request Jan 11, 2015
@G4MR
Copy link

G4MR commented Jan 14, 2015

The book for moving closure is still using boxed closures as an example: http://doc.rust-lang.org/book/closures.html

@steveklabnik
Copy link
Member

They should all be unboxed. Which are boxed?

@G4MR
Copy link

G4MR commented Jan 15, 2015

(e.g., move || x * x).

I'm pretty sure that syntax is invalid now with 1.0?

@sinistersnare
Copy link
Contributor

move || syntax is unboxed. proc is boxed.

@eddyb
Copy link
Member

eddyb commented Jan 15, 2015

The || syntax is the intended (unboxed) closure syntax. Right now it only works when a type hint is available (including bounded generics), and the awkward specific syntax is still needed in other cases (like let id = |&:x| x;) but because of this PR, that syntax can finally be removed, after || is made to properly infer the kind of closure.

@japaric japaric deleted the no-more-bc branch January 22, 2015 16:28
ehuss added a commit to ehuss/rust that referenced this pull request Apr 15, 2023
This test was ignored long ago in
rust-lang#20578 when the syntax for
closures was changed.

The current status is that a closure with an explicit `!` return type
will trigger the `unreachable_code` lint which appears to be the
original intent of the test
(rust-lang#16836). A closure without a
return type won't trigger the lint since the `!` type isn't inferred
(AFAIK). This restores the test to its original form.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove old (boxed) closures from the language