diff options
| author | Masaki Hara <ackie.h.gmai@gmail.com> | 2018-10-28 15:36:58 +0900 |
|---|---|---|
| committer | CrLF0710 <crlf0710@gmail.com> | 2019-04-05 02:26:56 +0800 |
| commit | 219097ecf6026954db100fb00089a2188915615d (patch) | |
| tree | 4f4fce629c2f44b5305df11f3cb7f1e0f98c39bf | |
| parent | 480dcb403caa90ecd2cc717ad4801805c010d3f6 (diff) | |
| download | rust-219097ecf6026954db100fb00089a2188915615d.tar.gz rust-219097ecf6026954db100fb00089a2188915615d.zip | |
Add unstable-book articles on fnbox and boxed_closure_impls.
| -rw-r--r-- | src/doc/unstable-book/src/library-features/boxed-closure-impls.md | 98 | ||||
| -rw-r--r-- | src/doc/unstable-book/src/library-features/fnbox.md | 252 |
2 files changed, 350 insertions, 0 deletions
diff --git a/src/doc/unstable-book/src/library-features/boxed-closure-impls.md b/src/doc/unstable-book/src/library-features/boxed-closure-impls.md new file mode 100644 index 00000000000..0c738d0f78e --- /dev/null +++ b/src/doc/unstable-book/src/library-features/boxed-closure-impls.md @@ -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. diff --git a/src/doc/unstable-book/src/library-features/fnbox.md b/src/doc/unstable-book/src/library-features/fnbox.md new file mode 100644 index 00000000000..3200601e557 --- /dev/null +++ b/src/doc/unstable-book/src/library-features/fnbox.md @@ -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. |
