I was responding to an issue on github when it dawned upon me that I really have no idea why Box<Fn()>
doesn't implement Fn()
. I think I worked around this once by just making my own fake Fn
trait, which is really kind of dumb if you think about it.
I’m guessing it’s because you’d then have to make Box<Fn()>
also implement FnOnce
but you cannot implement that for Box<Fn()>
because you cannot move the Fn
as a self
since it’s unsized - see FnBox
as a workaround.
impl<'a, A, R> Fn<A> for &'a Fn<A, Output=R> { }
impl<'a, A, R> FnMut<A> for &'a Fn<A, Output=R> { }
impl<'a, A, R> FnOnce<A> for &'a Fn<A, Output=R> { }
impl<'a, A, R> Fn<A> for &'a mut Fn<A, Output=R> { }
impl<'a, A, R> FnMut<A> for &'a mut Fn<A, Output=R> { }
impl<'a, A, R> FnOnce<A> for &'a mut Fn<A, Output=R> { }
impl<A, R> Fn<A> for Box<Fn<A, Output=R>> { }
impl<A, R> FnMut<A> for Box<Fn<A, Output=R>> { }
impl<A, R> FnOnce<A> for Box<Fn<A, Output=R>> { }
impl<'a, A, R> FnMut<A> for &'a mut FnMut<A, Output=R> { }
impl<'a, A, R> FnOnce<A> for &'a mut FnMut<A, Output=R> { }
impl<A, R> FnMut<A> for Box<FnMut<A, Output=R>> { }
impl<A, R> FnOnce<A> for Box<FnMut<A, Output=R>> { }
// no impl<A, R> FnOnce<A> for Box<FnOnce<A, Output=R>> { },
// but everything else works fine!
Edit: I subtly renamed the topic in response to the fact that impl Fn for &mut Fn
apparently does not exist either. (it used to read "why doesn't Box<Fn>
implement Fn
like &Fn
and &mut Fn
do?")
What you wrote there for the boxed versions is nothing but trait objects, which is not what you’d want in std - you’d want, for example, impl<A, F: Fn<A> + ?Sized> Fn<A> for Box<F>
. Once you get to FnOnce
you cannot do it if it’s ?Sized.
I also think there’s a separate coherence issue with FnOnce being implemented for Box - it would conflict with impl<A, F: FnOnce<A> + ?Sized> FnOnce<A> for Box<F>
.
It's a coherence issue. I think removing FnBox resolves it, but I'm not sure. I know I've tried to add the Box<Fn>
, Box<FnMut>
impls but it didn't compile at that time.
Would love for the basic case of;
let foo: Box<FnOnce()> = ...
foo()
to just work. FnBox
is just too much of a bad hack from users point and user doesn't care about how compiler makes it happen.
The coherence that @vitalyd mentions is possibly solved with specialization.
Had a simple language addition idea rolling in my head for months that would workaround the Box not working in your example.
Simply put it would allow you to identify the type that is normally by default anonymous; one for functions( & closures) and one the return value.
fn wrap(b: Box<Fn(&ShaderName) -> ShaderDefinitionFuture>) -> impl Fn(&ShaderName) -> ShaderDefinitionFuture {
|sn| b(sn)
}
pub type ShaderLoader = -> wrap;
Oops, I must be blind! I somehow missed the fact that those impls in std are generic over F
.
But here's another sample. Here, I add four new impls on top of those already existing in std.
The important thing here is that I've made the impl for Box<FnOnce>
require FnMut
instead of FnOnce
.
impl<'a, A, F> Fn<A> for &'a mut F
where F: Fn<A> + ?Sized { ... }
impl<A, F> Fn<A> for Box<F>
where F: Fn<A> + ?Sized { ... }
impl<A, F> FnMut<A> for Box<F>
where F: FnMut<A> + ?Sized { ... }
// NOTE: This one has weaker bounds than ideally possible
// due to the aforementioned problems with ?Sized.
impl<A, F> FnOnce<A> for Box<F>
where F: FnMut<A> + ?Sized { ... }
"Desirable" impls
Consider the kinds of impls that would be desirable (even if not possible due to the precise signatures of the Fn
traits) given these typical "effective meanings:"
- Closures:
- Suppose
MyFnOnce
does not implFnMut
. Then you must give up ownership to call it. - Suppose
MyFnMut
does not implFn
. Then you require exclusive access to call it.
- Suppose
- Asking for a generic function:
- Request
F: FnOnce
if you only need to call it once. - Request
F: FnMut
if you need to call it multiple times. - Request
F: Fn
if you need to call it while also sharing it.
- Request
- Supplying a type-erased closure to another API:
- Supply
Box::new(f)
to relinquish ownership. - Supply
&mut f
to give exclusive access. - Supply
&f
to give shared access.
- Supply
And suppose we have the following functions for testing if a trait is implemented:
// These functions test if the argument impls a Fn trait.
fn is_fn<F: Fn<(), Output=()>>(_: F) {}
fn is_mut<F: FnMut<(), Output=()>>(_: F) {}
fn is_once<F: FnOnce<(), Output=()>>(_: F) {}
Then here's a summary of all the things we would like to be able to do, and whether they work with todays impls or with the additional impls I presented above.
Call | Possible ever | Possible today | Possible with the extra impls |
---|---|---|---|
is_fn(&MyFn) |
x | x | x |
is_mut(&MyFn) |
x | x | x |
is_once(&MyFn) |
x | x | x |
is_fn(&mut MyFn) |
x | -- | x |
is_mut(&mut MyFn) |
x | x | x |
is_once(&mut MyFn) |
x | x | x |
is_fn(Box::new(MyFn)) |
x | -- | x |
is_mut(Box::new(MyFn)) |
x | -- | x |
is_once(Box::new(MyFn)) |
x | -- | x |
Call | Possible ever | Possible today | Possible with the extra impls |
---|---|---|---|
is_mut(&mut MyFnMut) |
x | x | x |
is_once(&mut MyFnMut) |
x | x | x |
is_mut(Box::new(MyFnMut)) |
x | -- | x |
is_once(Box::new(MyFnMut)) |
x | -- | x |
Call | Possible ever | Possible today | Possible with the extra impls |
---|---|---|---|
is_once(Box::new(MyFnOnce)) |
-- | -- | -- |
This runs into the coherence issue with FnBox
if you were to do this in std. I think we need to figure out what to do with it, ideally get rid of it.
My understanding is the compiler team doesn’t want to make any more special accommodations for Box
; if they did then it’s likely Box<FnOnce>
could be made to work and FnBox
could go away. At that point, and with specialization, a more flexible design could be made (I think).