Skip to content
Closed
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5c1cd6c
Update cargo
dtolnay Apr 3, 2019
a37c33b
Mark unix::ffi::OsStrExt methods as inline
sugar700 Apr 4, 2019
a964921
Use declare_lint_pass! and impl_lint_pass! in more places
hgallagher1993 Apr 4, 2019
d6b91fe
Update cargo
dtolnay Apr 4, 2019
1bf04c9
std: Upgrade `compiler_builtins` to fix wasi linkage
alexcrichton Apr 2, 2019
7994197
Make FnBox a subtrait of FnOnce.
qnighy Oct 28, 2018
059ec76
Add Fn* blanket impls for Box.
qnighy Oct 28, 2018
480dcb4
Add tests for boxed_closure_impls.
qnighy Oct 28, 2018
219097e
Add unstable-book articles on fnbox and boxed_closure_impls.
qnighy Oct 28, 2018
e55d82c
Fix expectations on some ui tests involving FnOnce.
qnighy Oct 28, 2018
4dcd6cc
Fix failing tests.
qnighy Feb 3, 2019
a38f292
We already have unsized_locals in stage0.
qnighy Feb 10, 2019
45c0b28
Remove FnBox specialization of impl FnOnce for Box<impl FnOnce>.
qnighy Feb 11, 2019
ecc3e89
Stabilize boxed_closure_impls in 1.35.0.
crlf0710 Feb 11, 2019
440e873
Simplify fnbox docs.
qnighy Feb 11, 2019
7a63c7f
Add ignore to doc code
qnighy Feb 11, 2019
471db2b
wasm32: Default to a "static" relocation model
alexcrichton Apr 4, 2019
8c0e786
Rollup merge of #59500 - crlf0710:boxed-closure-impls, r=cramertj
Centril Apr 5, 2019
1f05de7
Rollup merge of #59643 - alexcrichton:wasi-symbols, r=sanxiyn
Centril Apr 5, 2019
bc995f4
Rollup merge of #59681 - dtolnay:cargo, r=alexcrichton
Centril Apr 5, 2019
6070d47
Rollup merge of #59690 - xfix:patch-17, r=cramertj
Centril Apr 5, 2019
3600e4d
Rollup merge of #59702 - hgallagher1993:origin, r=Centril
Centril Apr 5, 2019
c03fa7b
Rollup merge of #59712 - alexcrichton:wasm-static-not-pic, r=eddyb
Centril Apr 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Simplify fnbox docs.
  • Loading branch information
qnighy authored and crlf0710 committed Apr 4, 2019
commit 440e873a4739e7cb8f6aec0f675ca3796c336e60
250 changes: 15 additions & 235 deletions src/doc/unstable-book/src/library-features/fnbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,247 +6,27 @@ The tracking issue for this feature is [#28796]

------------------------

As an analogy to `&dyn Fn()` and `&mut dyn FnMut()`, you may have expected
`Box<dyn FnOnce()>` to work. But it hadn't until the recent improvement!
`FnBox` had been a **temporary** solution for this until we are able to pass
trait objects by value.

See [`boxed_closure_impls`][boxed_closure_impls] for the newer approach.

[boxed_closure_impls]: library-features/boxed-closure-impls.html

## Usage

If you want to box `FnOnce` closures, you can use `Box<dyn FnBox()>` instead of `Box<dyn FnOnce()>`.
This had been a temporary alternative to the following impls:

```rust
#![feature(fnbox)]

use std::boxed::FnBox;

fn main() {
let resource = "hello".to_owned();
// Create a boxed once-callable closure
let f: Box<dyn FnBox() -> String> = Box::new(|| resource);

// Call it
let s = f();
println!("{}", s);
}
impl<A, F> FnOnce for Box<F> where F: FnOnce<A> + ?Sized {}
impl<A, F> FnMut for Box<F> where F: FnMut<A> + ?Sized {}
impl<A, F> Fn for Box<F> where F: Fn<A> + ?Sized {}
```

## How `Box<dyn FnOnce()>` did not work

**Spoiler**: [`boxed_closure_impls`][boxed_closure_impls] actually implements
`Box<dyn FnOnce()>`! This didn't work because we lacked features like
[`unsized_locals`][unsized_locals] for a long time. Therefore, this section
just explains historical reasons for `FnBox`.

[unsized_locals]: language-features/unsized-locals.html

### First approach: just provide `Box` adapter impl

The first (and natural) attempt for `Box<dyn FnOnce()>` would look like:

```rust,ignore
impl<A, F: FnOnce<A> + ?Sized> FnOnce<A> for Box<F> {
type Output = <F as FnOnce<A>>::Output;

extern "rust-call" fn call_once(self, args: A) -> Self::Output {
<F as FnOnce<A>>::call_once(*self, args)
}
}
```

However, this doesn't work. We have to relax the `Sized` bound for `F` because
we expect trait objects here, but `*self` must be `Sized` because it is passed
as a function argument.

### The second attempt: add `FnOnce::call_box`

One may come up with this workaround: modify `FnOnce`'s definition like this:

```rust,ignore
pub trait FnOnce<Args> {
type Output;

extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
// Add this new method
extern "rust-call" fn call_box(self: Box<Self>, args: Args) -> Self::Output;
}
```

...and then, modify the `impl` like this:

```rust,ignore
impl<A, F: FnOnce<A> + ?Sized> FnOnce<A> for Box<F> {
type Output = <F as FnOnce<A>>::Output;

extern "rust-call" fn call_once(self, args: A) -> Self::Output {
// We can use `call_box` here!
<F as FnOnce<A>>::call_box(self, args)
}
// We'll have to define this in every impl of `FnOnce`.
extern "rust-call" fn call_box(self: Box<Self>, args: A) -> Self::Output {
<F as FnOnce<A>>::call_box(*self, args)
}
}
```

What's wrong with this? The problem here is crates:

- `FnOnce` is in `libcore`, as it shouldn't depend on allocations.
- `Box` is in `liballoc`, as it:s the very allocated pointer.

It is impossible to add `FnOnce::call_box` because it is reverse-dependency.

There's another problem: `call_box` can't have defaults.
`default impl` from the specialization RFC may resolve this problem.

### The third attempt: add `FnBox` that contains `call_box`

`call_box` can't reside in `FnOnce`, but how about defining a new trait in
`liballoc`?

`FnBox` is almost a copy of `FnOnce`, but with `call_box`:

```rust,ignore
pub trait FnBox<Args> {
type Output;

extern "rust-call" fn call_box(self: Box<Self>, args: Args) -> Self::Output;
}
```

For `Sized` types (from which we coerce into `dyn FnBox`), we define
the blanket impl that proxies calls to `FnOnce`:

```rust,ignore
impl<A, F: FnOnce<A>> FnBox<A> for F {
type Output = <F as FnOnce<A>>::Output;
The impls are parallel to these (relatively old) impls:

extern "rust-call" fn call_box(self: Box<Self>, args: A) -> Self::Output {
// Here we assume `F` to be sized.
<F as FnOnce<A>>::call_once(*self, args)
}
}
```

Now it looks like that we can define `FnOnce` for `Box<F>`.

```rust,ignore
impl<A, F: FnBox<A> + ?Sized> FnOnce<A> for Box<F> {
type Output = <F as FnOnce<A>>::Output;

extern "rust-call" fn call_once(self, args: A) -> Self::Output {
<F as FnBox<A>>::call_box(self, args)
}
}
```

## Limitations of `FnBox`

### Interaction with HRTB

Firstly, the actual implementation is different from the one presented above.
Instead of implementing `FnOnce` for `Box<impl FnBox>`, `liballoc` only
implements `FnOnce` for `Box<dyn FnBox>`.

```rust,ignore
impl<'a, A, R> FnOnce<A> for Box<dyn FnBox<A, Output = R> + 'a> {
type Output = R;

extern "rust-call" fn call_once(self, args: A) -> Self::Output {
FnBox::call_box(*self, args)
}
}

// Sendable variant
impl<'a, A, R> FnOnce<A> for Box<dyn FnBox<A, Output = R> + Send + 'a> {
type Output = R;

extern "rust-call" fn call_once(self, args: A) -> Self::Output {
FnBox::call_box(*self, args)
}
}
```

The consequence is that the following example doesn't work:

```rust,compile_fail
#![feature(fnbox)]

use std::boxed::FnBox;

fn main() {
let f: Box<dyn FnBox(&i32)> = Box::new(|x| println!("{}", x));
f(42);
}
```

Note that `dyn FnBox(&i32)` desugars to
`dyn for<'r> FnBox<(&'r i32,), Output = ()>`.
It isn't covered in `dyn FnBox<A, Output = R> + 'a` or
`dyn FnBox<A, Output = R> + Send + 'a` due to HRTB.

### Interaction with `Fn`/`FnMut`

It would be natural to have the following impls:

```rust,ignore
impl<A, F: FnMut<A> + ?Sized> FnMut<A> for Box<F> {
// ...
}
impl<A, F: Fn<A> + ?Sized> Fn<A> for Box<F> {
// ...
}
```

However, we hadn't been able to write these in presense of `FnBox`
(until [`boxed_closure_impls`][boxed_closure_impls] lands).

To have `FnMut<A>` for `Box<F>`, we should have (at least) this impl:

```rust,ignore
// Note here we only impose `F: FnMut<A>`.
// If we can write `F: FnOnce<A>` here, that will resolve all problems.
impl<A, F: FnMut<A> + ?Sized> FnOnce<A> for Box<F> {
// ...
}
```

Unfortunately, the compiler complains that it **overlaps** with our
`dyn FnBox()` impls. At first glance, the overlap must not happen.
The `A` generic parameter does the trick here: due to coherence rules,
a downstream crate may define the following impl:

```rust,ignore
struct MyStruct;
impl<'a> FnMut<MyStruct> for dyn FnBox<MyStruct, Output = ()> + 'a {
// ...
}
```rust
impl<A, F> FnOnce for &mut F where F: FnMut<A> + ?Sized {}
impl<A, F> FnMut for &mut F where F: FnMut<A> + ?Sized {}
impl<A, F> Fn for &mut F where F: Fn<A> + ?Sized {}
impl<A, F> FnOnce for &F where F: Fn<A> + ?Sized {}
impl<A, F> FnMut for &F where F: Fn<A> + ?Sized {}
impl<A, F> Fn for &F where F: Fn<A> + ?Sized {}
```

The trait solver doesn't know that `A` is always a tuple type, so this is
still possible. With this in mind, the compiler emits the overlap error.

## Modification

For compatibility with [`boxed_closure_impls`][boxed_closure_impls],
we now have a slightly modified version of `FnBox`:
Before the introduction of [`unsized_locals`][unsized_locals], we had been unable to provide the former impls. That means, unlike `&dyn Fn()` or `&mut dyn FnMut()` we could not use `Box<dyn FnOnce()>` at that time.

```rust,ignore
// It's now a subtrait of `FnOnce`
pub trait FnBox<Args>: FnOnce<Args> {
// now uses FnOnce::Output
// type Output;

extern "rust-call" fn call_box(self: Box<Self>, args: Args) -> Self::Output;
}
```

## The future of `fnbox`
[unsized_locals]: language-features/unsized-locals.html

`FnBox` has long been considered a temporary solution for `Box<FnOnce>`
problem. Since we have [`boxed_closure_impls`][boxed_closure_impls] now,
it may be deprecated and removed in the future.
`FnBox()` is an alternative approach to `Box<dyn FnBox()>` is delegated to `FnBox::call_box` which doesn't need unsized locals. As we now have `Box<dyn FnOnce()>` working, the `fnbox` feature is going to be removed.