Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add unstable-book articles on fnbox and boxed_closure_impls.
- Loading branch information
Showing
2 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
98 changes: 98 additions & 0 deletions
98
src/doc/unstable-book/src/library-features/boxed-closure-impls.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# `boxed_closure_impls` | ||
|
||
The tracking issue for this feature is [#48055] | ||
|
||
[#48055]: https://github.com/rust-lang/rust/issues/48055 | ||
|
||
------------------------ | ||
|
||
This includes the following blanket impls for closure traits: | ||
|
||
```rust,ignore | ||
impl<A, F: FnOnce<A> + ?Sized> FnOnce for Box<F> { | ||
// ... | ||
} | ||
impl<A, F: FnMut<A> + ?Sized> FnMut for Box<F> { | ||
// ... | ||
} | ||
impl<A, F: Fn<A> + ?Sized> Fn for Box<F> { | ||
// ... | ||
} | ||
``` | ||
|
||
## Usage | ||
|
||
`Box` can be used almost transparently. You can even use `Box<dyn FnOnce>` now. | ||
|
||
```rust | ||
#![feature(boxed_closure_impls)] | ||
|
||
fn main() { | ||
let resource = "hello".to_owned(); | ||
// Create a boxed once-callable closure | ||
let f: Box<dyn FnOnce(&i32)> = Box::new(|x| { | ||
let s = resource; | ||
println!("{}", x); | ||
println!("{}", s); | ||
}); | ||
|
||
// Call it | ||
f(); | ||
} | ||
``` | ||
|
||
## The reason for instability | ||
|
||
This is unstable because of the first impl. | ||
|
||
It would have been easy if we're allowed to tighten the bound: | ||
|
||
```rust,ignore | ||
impl<A, F: FnMut<A> + ?Sized> FnOnce for Box<F> { | ||
// ... | ||
} | ||
``` | ||
|
||
However, `Box<dyn FnOnce()>` drops out of the modified impl. | ||
To rescue this, we had had a temporary solution called [`fnbox`][fnbox]. | ||
|
||
[fnbox]: library-features/fnbox.html | ||
|
||
Unfortunately, due to minor coherence reasons, `fnbox` and | ||
`FnOnce for Box<impl FnMut>` had not been able to coexist. | ||
We had preferred `fnbox` for the time being. | ||
|
||
Now, as [`unsized_locals`][unsized_locals] is implemented, we can just write the | ||
original impl: | ||
|
||
[unsized_locals]: language-features/unsized-locals.html | ||
|
||
```rust,ignore | ||
impl<A, F: FnOnce<A> + ?Sized> FnOnce for Box<F> { | ||
type Output = <F as FnOnce<A>>::Output; | ||
extern "rust-call" fn call_once(self, args: A) -> Self::Output { | ||
// *self is an unsized rvalue | ||
<F as FnOnce<A>>::call_once(*self, args) | ||
} | ||
} | ||
``` | ||
|
||
However, since `unsized_locals` is a very young feature, we're careful about | ||
this `FnOnce` impl now. | ||
|
||
There's another reason for instability: for compatibility with `fnbox`, | ||
we currently allow specialization of the `Box<impl FnOnce>` impl: | ||
|
||
```rust,ignore | ||
impl<A, F: FnOnce<A> + ?Sized> FnOnce for Box<F> { | ||
type Output = <F as FnOnce<A>>::Output; | ||
// we have "default" here | ||
default extern "rust-call" fn call_once(self, args: A) -> Self::Output { | ||
<F as FnOnce<A>>::call_once(*self, args) | ||
} | ||
} | ||
``` | ||
|
||
This isn't what we desire in the long term. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
# `fnbox` | ||
|
||
The tracking issue for this feature is [#28796] | ||
|
||
[#28796]: https://github.com/rust-lang/rust/issues/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()>`. | ||
|
||
```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); | ||
} | ||
``` | ||
|
||
## 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; | ||
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 { | ||
// ... | ||
} | ||
``` | ||
|
||
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`: | ||
|
||
```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` | ||
|
||
`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. |