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 0000000000000..0c738d0f78e03 --- /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 + ?Sized> FnOnce for Box { + // ... +} +impl + ?Sized> FnMut for Box { + // ... +} +impl + ?Sized> Fn for Box { + // ... +} +``` + +## Usage + +`Box` can be used almost transparently. You can even use `Box` now. + +```rust +#![feature(boxed_closure_impls)] + +fn main() { + let resource = "hello".to_owned(); + // Create a boxed once-callable closure + let f: Box = 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 + ?Sized> FnOnce for Box { + // ... +} +``` + +However, `Box` 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` 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 + ?Sized> FnOnce for Box { + type Output = >::Output; + + extern "rust-call" fn call_once(self, args: A) -> Self::Output { + // *self is an unsized rvalue + >::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: + +```rust,ignore +impl + ?Sized> FnOnce for Box { + type Output = >::Output; + + // we have "default" here + default extern "rust-call" fn call_once(self, args: A) -> Self::Output { + >::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 0000000000000..3200601e557f6 --- /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` 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` instead of `Box`. + +```rust +#![feature(fnbox)] + +use std::boxed::FnBox; + +fn main() { + let resource = "hello".to_owned(); + // Create a boxed once-callable closure + let f: Box String> = Box::new(|| resource); + + // Call it + let s = f(); + println!("{}", s); +} +``` + +## How `Box` did not work + +**Spoiler**: [`boxed_closure_impls`][boxed_closure_impls] actually implements +`Box`! 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` would look like: + +```rust,ignore +impl + ?Sized> FnOnce for Box { + type Output = >::Output; + + extern "rust-call" fn call_once(self, args: A) -> Self::Output { + >::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 { + 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, args: Args) -> Self::Output; +} +``` + +...and then, modify the `impl` like this: + +```rust,ignore +impl + ?Sized> FnOnce for Box { + type Output = >::Output; + + extern "rust-call" fn call_once(self, args: A) -> Self::Output { + // We can use `call_box` here! + >::call_box(self, args) + } + // We'll have to define this in every impl of `FnOnce`. + extern "rust-call" fn call_box(self: Box, args: A) -> Self::Output { + >::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 { + type Output; + + extern "rust-call" fn call_box(self: Box, 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> FnBox for F { + type Output = >::Output; + + extern "rust-call" fn call_box(self: Box, args: A) -> Self::Output { + // Here we assume `F` to be sized. + >::call_once(*self, args) + } +} +``` + +Now it looks like that we can define `FnOnce` for `Box`. + +```rust,ignore +impl + ?Sized> FnOnce for Box { + type Output = >::Output; + + extern "rust-call" fn call_once(self, args: A) -> Self::Output { + >::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`, `liballoc` only +implements `FnOnce` for `Box`. + +```rust,ignore +impl<'a, A, R> FnOnce for Box + '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 for Box + 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 = 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` or +`dyn FnBox + Send + 'a` due to HRTB. + +### Interaction with `Fn`/`FnMut` + +It would be natural to have the following impls: + +```rust,ignore +impl + ?Sized> FnMut for Box { + // ... +} +impl + ?Sized> Fn for Box { + // ... +} +``` + +However, we hadn't been able to write these in presense of `FnBox` +(until [`boxed_closure_impls`][boxed_closure_impls] lands). + +To have `FnMut` for `Box`, we should have (at least) this impl: + +```rust,ignore +// Note here we only impose `F: FnMut`. +// If we can write `F: FnOnce` here, that will resolve all problems. +impl + ?Sized> FnOnce for Box { + // ... +} +``` + +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 for dyn FnBox + '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: FnOnce { + // now uses FnOnce::Output + // type Output; + + extern "rust-call" fn call_box(self: Box, args: Args) -> Self::Output; +} +``` + +## The future of `fnbox` + +`FnBox` has long been considered a temporary solution for `Box` +problem. Since we have [`boxed_closure_impls`][boxed_closure_impls] now, +it may be deprecated and removed in the future.