Skip to content

Commit 219097e

Browse files
qnighycrlf0710
authored andcommitted
Add unstable-book articles on fnbox and boxed_closure_impls.
1 parent 480dcb4 commit 219097e

File tree

2 files changed

+350
-0
lines changed

2 files changed

+350
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# `boxed_closure_impls`
2+
3+
The tracking issue for this feature is [#48055]
4+
5+
[#48055]: https://github.com/rust-lang/rust/issues/48055
6+
7+
------------------------
8+
9+
This includes the following blanket impls for closure traits:
10+
11+
```rust,ignore
12+
impl<A, F: FnOnce<A> + ?Sized> FnOnce for Box<F> {
13+
// ...
14+
}
15+
impl<A, F: FnMut<A> + ?Sized> FnMut for Box<F> {
16+
// ...
17+
}
18+
impl<A, F: Fn<A> + ?Sized> Fn for Box<F> {
19+
// ...
20+
}
21+
```
22+
23+
## Usage
24+
25+
`Box` can be used almost transparently. You can even use `Box<dyn FnOnce>` now.
26+
27+
```rust
28+
#![feature(boxed_closure_impls)]
29+
30+
fn main() {
31+
let resource = "hello".to_owned();
32+
// Create a boxed once-callable closure
33+
let f: Box<dyn FnOnce(&i32)> = Box::new(|x| {
34+
let s = resource;
35+
println!("{}", x);
36+
println!("{}", s);
37+
});
38+
39+
// Call it
40+
f();
41+
}
42+
```
43+
44+
## The reason for instability
45+
46+
This is unstable because of the first impl.
47+
48+
It would have been easy if we're allowed to tighten the bound:
49+
50+
```rust,ignore
51+
impl<A, F: FnMut<A> + ?Sized> FnOnce for Box<F> {
52+
// ...
53+
}
54+
```
55+
56+
However, `Box<dyn FnOnce()>` drops out of the modified impl.
57+
To rescue this, we had had a temporary solution called [`fnbox`][fnbox].
58+
59+
[fnbox]: library-features/fnbox.html
60+
61+
Unfortunately, due to minor coherence reasons, `fnbox` and
62+
`FnOnce for Box<impl FnMut>` had not been able to coexist.
63+
We had preferred `fnbox` for the time being.
64+
65+
Now, as [`unsized_locals`][unsized_locals] is implemented, we can just write the
66+
original impl:
67+
68+
[unsized_locals]: language-features/unsized-locals.html
69+
70+
```rust,ignore
71+
impl<A, F: FnOnce<A> + ?Sized> FnOnce for Box<F> {
72+
type Output = <F as FnOnce<A>>::Output;
73+
74+
extern "rust-call" fn call_once(self, args: A) -> Self::Output {
75+
// *self is an unsized rvalue
76+
<F as FnOnce<A>>::call_once(*self, args)
77+
}
78+
}
79+
```
80+
81+
However, since `unsized_locals` is a very young feature, we're careful about
82+
this `FnOnce` impl now.
83+
84+
There's another reason for instability: for compatibility with `fnbox`,
85+
we currently allow specialization of the `Box<impl FnOnce>` impl:
86+
87+
```rust,ignore
88+
impl<A, F: FnOnce<A> + ?Sized> FnOnce for Box<F> {
89+
type Output = <F as FnOnce<A>>::Output;
90+
91+
// we have "default" here
92+
default extern "rust-call" fn call_once(self, args: A) -> Self::Output {
93+
<F as FnOnce<A>>::call_once(*self, args)
94+
}
95+
}
96+
```
97+
98+
This isn't what we desire in the long term.
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# `fnbox`
2+
3+
The tracking issue for this feature is [#28796]
4+
5+
[#28796]: https://github.com/rust-lang/rust/issues/28796
6+
7+
------------------------
8+
9+
As an analogy to `&dyn Fn()` and `&mut dyn FnMut()`, you may have expected
10+
`Box<dyn FnOnce()>` to work. But it hadn't until the recent improvement!
11+
`FnBox` had been a **temporary** solution for this until we are able to pass
12+
trait objects by value.
13+
14+
See [`boxed_closure_impls`][boxed_closure_impls] for the newer approach.
15+
16+
[boxed_closure_impls]: library-features/boxed-closure-impls.html
17+
18+
## Usage
19+
20+
If you want to box `FnOnce` closures, you can use `Box<dyn FnBox()>` instead of `Box<dyn FnOnce()>`.
21+
22+
```rust
23+
#![feature(fnbox)]
24+
25+
use std::boxed::FnBox;
26+
27+
fn main() {
28+
let resource = "hello".to_owned();
29+
// Create a boxed once-callable closure
30+
let f: Box<dyn FnBox() -> String> = Box::new(|| resource);
31+
32+
// Call it
33+
let s = f();
34+
println!("{}", s);
35+
}
36+
```
37+
38+
## How `Box<dyn FnOnce()>` did not work
39+
40+
**Spoiler**: [`boxed_closure_impls`][boxed_closure_impls] actually implements
41+
`Box<dyn FnOnce()>`! This didn't work because we lacked features like
42+
[`unsized_locals`][unsized_locals] for a long time. Therefore, this section
43+
just explains historical reasons for `FnBox`.
44+
45+
[unsized_locals]: language-features/unsized-locals.html
46+
47+
### First approach: just provide `Box` adapter impl
48+
49+
The first (and natural) attempt for `Box<dyn FnOnce()>` would look like:
50+
51+
```rust,ignore
52+
impl<A, F: FnOnce<A> + ?Sized> FnOnce<A> for Box<F> {
53+
type Output = <F as FnOnce<A>>::Output;
54+
55+
extern "rust-call" fn call_once(self, args: A) -> Self::Output {
56+
<F as FnOnce<A>>::call_once(*self, args)
57+
}
58+
}
59+
```
60+
61+
However, this doesn't work. We have to relax the `Sized` bound for `F` because
62+
we expect trait objects here, but `*self` must be `Sized` because it is passed
63+
as a function argument.
64+
65+
### The second attempt: add `FnOnce::call_box`
66+
67+
One may come up with this workaround: modify `FnOnce`'s definition like this:
68+
69+
```rust,ignore
70+
pub trait FnOnce<Args> {
71+
type Output;
72+
73+
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
74+
// Add this new method
75+
extern "rust-call" fn call_box(self: Box<Self>, args: Args) -> Self::Output;
76+
}
77+
```
78+
79+
...and then, modify the `impl` like this:
80+
81+
```rust,ignore
82+
impl<A, F: FnOnce<A> + ?Sized> FnOnce<A> for Box<F> {
83+
type Output = <F as FnOnce<A>>::Output;
84+
85+
extern "rust-call" fn call_once(self, args: A) -> Self::Output {
86+
// We can use `call_box` here!
87+
<F as FnOnce<A>>::call_box(self, args)
88+
}
89+
// We'll have to define this in every impl of `FnOnce`.
90+
extern "rust-call" fn call_box(self: Box<Self>, args: A) -> Self::Output {
91+
<F as FnOnce<A>>::call_box(*self, args)
92+
}
93+
}
94+
```
95+
96+
What's wrong with this? The problem here is crates:
97+
98+
- `FnOnce` is in `libcore`, as it shouldn't depend on allocations.
99+
- `Box` is in `liballoc`, as it:s the very allocated pointer.
100+
101+
It is impossible to add `FnOnce::call_box` because it is reverse-dependency.
102+
103+
There's another problem: `call_box` can't have defaults.
104+
`default impl` from the specialization RFC may resolve this problem.
105+
106+
### The third attempt: add `FnBox` that contains `call_box`
107+
108+
`call_box` can't reside in `FnOnce`, but how about defining a new trait in
109+
`liballoc`?
110+
111+
`FnBox` is almost a copy of `FnOnce`, but with `call_box`:
112+
113+
```rust,ignore
114+
pub trait FnBox<Args> {
115+
type Output;
116+
117+
extern "rust-call" fn call_box(self: Box<Self>, args: Args) -> Self::Output;
118+
}
119+
```
120+
121+
For `Sized` types (from which we coerce into `dyn FnBox`), we define
122+
the blanket impl that proxies calls to `FnOnce`:
123+
124+
```rust,ignore
125+
impl<A, F: FnOnce<A>> FnBox<A> for F {
126+
type Output = <F as FnOnce<A>>::Output;
127+
128+
extern "rust-call" fn call_box(self: Box<Self>, args: A) -> Self::Output {
129+
// Here we assume `F` to be sized.
130+
<F as FnOnce<A>>::call_once(*self, args)
131+
}
132+
}
133+
```
134+
135+
Now it looks like that we can define `FnOnce` for `Box<F>`.
136+
137+
```rust,ignore
138+
impl<A, F: FnBox<A> + ?Sized> FnOnce<A> for Box<F> {
139+
type Output = <F as FnOnce<A>>::Output;
140+
141+
extern "rust-call" fn call_once(self, args: A) -> Self::Output {
142+
<F as FnBox<A>>::call_box(self, args)
143+
}
144+
}
145+
```
146+
147+
## Limitations of `FnBox`
148+
149+
### Interaction with HRTB
150+
151+
Firstly, the actual implementation is different from the one presented above.
152+
Instead of implementing `FnOnce` for `Box<impl FnBox>`, `liballoc` only
153+
implements `FnOnce` for `Box<dyn FnBox>`.
154+
155+
```rust,ignore
156+
impl<'a, A, R> FnOnce<A> for Box<dyn FnBox<A, Output = R> + 'a> {
157+
type Output = R;
158+
159+
extern "rust-call" fn call_once(self, args: A) -> Self::Output {
160+
FnBox::call_box(*self, args)
161+
}
162+
}
163+
164+
// Sendable variant
165+
impl<'a, A, R> FnOnce<A> for Box<dyn FnBox<A, Output = R> + Send + 'a> {
166+
type Output = R;
167+
168+
extern "rust-call" fn call_once(self, args: A) -> Self::Output {
169+
FnBox::call_box(*self, args)
170+
}
171+
}
172+
```
173+
174+
The consequence is that the following example doesn't work:
175+
176+
```rust,compile_fail
177+
#![feature(fnbox)]
178+
179+
use std::boxed::FnBox;
180+
181+
fn main() {
182+
let f: Box<dyn FnBox(&i32)> = Box::new(|x| println!("{}", x));
183+
f(42);
184+
}
185+
```
186+
187+
Note that `dyn FnBox(&i32)` desugars to
188+
`dyn for<'r> FnBox<(&'r i32,), Output = ()>`.
189+
It isn't covered in `dyn FnBox<A, Output = R> + 'a` or
190+
`dyn FnBox<A, Output = R> + Send + 'a` due to HRTB.
191+
192+
### Interaction with `Fn`/`FnMut`
193+
194+
It would be natural to have the following impls:
195+
196+
```rust,ignore
197+
impl<A, F: FnMut<A> + ?Sized> FnMut<A> for Box<F> {
198+
// ...
199+
}
200+
impl<A, F: Fn<A> + ?Sized> Fn<A> for Box<F> {
201+
// ...
202+
}
203+
```
204+
205+
However, we hadn't been able to write these in presense of `FnBox`
206+
(until [`boxed_closure_impls`][boxed_closure_impls] lands).
207+
208+
To have `FnMut<A>` for `Box<F>`, we should have (at least) this impl:
209+
210+
```rust,ignore
211+
// Note here we only impose `F: FnMut<A>`.
212+
// If we can write `F: FnOnce<A>` here, that will resolve all problems.
213+
impl<A, F: FnMut<A> + ?Sized> FnOnce<A> for Box<F> {
214+
// ...
215+
}
216+
```
217+
218+
Unfortunately, the compiler complains that it **overlaps** with our
219+
`dyn FnBox()` impls. At first glance, the overlap must not happen.
220+
The `A` generic parameter does the trick here: due to coherence rules,
221+
a downstream crate may define the following impl:
222+
223+
```rust,ignore
224+
struct MyStruct;
225+
impl<'a> FnMut<MyStruct> for dyn FnBox<MyStruct, Output = ()> + 'a {
226+
// ...
227+
}
228+
```
229+
230+
The trait solver doesn't know that `A` is always a tuple type, so this is
231+
still possible. With this in mind, the compiler emits the overlap error.
232+
233+
## Modification
234+
235+
For compatibility with [`boxed_closure_impls`][boxed_closure_impls],
236+
we now have a slightly modified version of `FnBox`:
237+
238+
```rust,ignore
239+
// It's now a subtrait of `FnOnce`
240+
pub trait FnBox<Args>: FnOnce<Args> {
241+
// now uses FnOnce::Output
242+
// type Output;
243+
244+
extern "rust-call" fn call_box(self: Box<Self>, args: Args) -> Self::Output;
245+
}
246+
```
247+
248+
## The future of `fnbox`
249+
250+
`FnBox` has long been considered a temporary solution for `Box<FnOnce>`
251+
problem. Since we have [`boxed_closure_impls`][boxed_closure_impls] now,
252+
it may be deprecated and removed in the future.

0 commit comments

Comments
 (0)