It is not possible to make function/closure combinators that return function pointers, even though it is possible to return a function pointer that wraps a trait associated function.
Usecase
The main usecase is efficiently allowing erganomic wrappers for callbacks in C ffi.
For example, a wrapper around libc::signal that automatically wraps your signal handler so it saves and restores errno as required by the POSIX standard.
The Trait
#![feature(unboxed_closures)]
#![feature(tuple_trait)]
use std::marker::Tuple;
trait FnStatic<Args>: Fn<Args> where Args: Tuple {
fn call_static(args: Args) -> Self::Output;
}
This trait would be implemented for any implementor of Fn which is coercible to a function pointer.
Alternatively, instead of adding a new trait, equivalent functionality could be achieved by adding Default to this list of traits implemented by function item types (they are ZSTs, after all).
Wrapping a trait-associated function
This is currently possible, but it would have much less boilerplate if print_it was a free function.
I would certainly be more in favour of the Default solution than FnStatic. With the FnStatic approach, it’s not clear how to actually call such a function in a stable manner (since call, and the fact it accepts tuples, is currently unstable), whereas F::default()() is obvious. I also think that FnStatic is a confusing name, because implementations are not required to be 'static.
The ideal syntax for calling a generic function item would be just F(). A separate call_static seems unnecessary, since the type Fis the function item that is called.
That would be very surprising, because <F: FnStatic(i32)> introduces F into the type namespace only, while F(42); requires F to be added to the value namespace. It’s not impossible for a type parameter to add to the value namespace, but it seems like a bit of a hack to have a rule like:
If we can find FnStatic somewhere in the supertraits of a generic parameter F, add it to the value namespace.
So even though F(42)looks very clean, I think F::default()(42) works better with how Rust is today.
Given that the trait bound does introduce associated functions of the trait to the value namespace, it's not unimaginable that in this very special case of function items (the kind of type that those associated functions themselves are), the trait would include the thing itself as a function. Kind of like <F as FnStatic>::self. But yes, probably that kind of thing should be syntactically distinct.
On a related note, we can't currently name function items as types, so how would the caller specify the generic parameter?
Is the idea that the compiler emits a unique wrapper function for each function wrapped via this trait? I don't see any other way to make the wrapper be (convertible to) a true function pointer (rather than a Fn* trait object that cannot be passed to an FFI interface expecting a function pointer), but it is potentially a lot of code duplication. (This is how your StaticPrinter example works today, though, so probably we can live with it.)
Yes, that is exactly the idea, to leverage the monomorphization pass.
This is actually how a lot of closures combinators already get compiled, and it's part of how iterator combinators like map manage to have actually decent performance.
For what it’s worth, I recently made a macro-based polyfill for this feature on stable Rust. However, there are several convenience limitations, and I’m not fully certain whether it’s sound. I would very much like to switch this code to a native feature.
one way to work around that is to use syntax that tells Rust to look in the type namespace:
pub fn foo<F: FnStatic(i32) -> u32>(v: i32) -> u32 {
// like <T>::VALUE syntax for associated consts
<F>(v) // or `<T as FnStatic(i32) -> u32>(v)` to disambiguate if it has multiple `FnStatic` impls
}
Making fn pointers impl Default might make some existing code unsound (assuming this is sound, not 100% sure). I don't know if such code exists but it's something to atleast think about.
mod seacret {
// this function *should* be marked `unsafe` but because its private
// it doesn't really _need_ to be
fn private_function(ptr: *const u8) -> u8 {
unsafe { *ptr }
}
// we make our getter `unsafe` so safe code cannot call `private_function`
/// # Safety
/// The returned function must not be exposed to untrusted code
/// and it must not be called with a pointer that is not valid for
/// reads
pub unsafe fn get_private_function() -> fn(*const u8) -> u8 {
private_function
}
}
fn get_default<T: Default>(_f: unsafe fn() -> T) -> T {
T::default()
}
fn main() {
// currently this code fails to compile, but making `fn-pointer`s `impl Default`
// changes that
let f = get_default(seacret::get_private_function);
let ub = f(0 as *const u8);
}
A function pointer type (e.g. fn(*const u8) -> u8) is not a ZST, and would never implement Default. The same idea almost works if the unsafe function returns the private function item as an abstract type:
/// # Safety: Must not call!
pub unsafe fn never_call_this() -> impl Fn() /* + Default */ {
/// This private "safe" function must never be called
fn cause_ub() {
unsafe { std::hint::unreachable_unchecked() }
}
cause_ub
}
fn main() {
let mut f = Default::default();
if false {
/// Safety: This is never executed
unsafe {
f = never_call_this();
}
}
let ub = f();
}
But that also shows why this isn't quite enough: Even if the concrete type (the fn-item fn() {cause_ub}) did implement Default, the signature only promises to return some impl Fn() and the caller cannot assume it to also be Default. If the unsafe function did return impl Fn() + Default, its safety argument would break down.
fn pointers can't impl Default since there is no default they could have. fn item's types (which are notfn pointers, they are zero-sized types where every function has its own separate type where the type encodes which function it is) can implement Default, since they have only one possible value.
( Maybe FnItem could be a better name for the trait? )
While function items being Default might be the simplest solution to add, it feels lacking:
Firstly, I find F::default()() obfuscating, as it is visually quite close to just F::default(). The point of the expression is that F represents some function that we call, but it is literally first calling another function to get the thing to call. Any one of F(), <F>(), F::call(), F::call_static() or F::call_item() would be clearer.
More importantly, a generic bound of F: Fn() + Default doesn't rule out stateful closures. While they currently aren't, closures could potentially be Default in the future, and nightly features allow implementing Fn() for custom types anyway. With stateful closures as a possibility, there would be a subtle difference in behaviour between various reasonable ways to implement the same wrapper:
fn do_twice<F: Default + Fn()>(_: F) -> impl Default + Fn() {
fn inner1<F: Default + Fn()>() {
F::default()();
F::default()();
}
fn inner2<F: Default + Fn()>() {
let f = F::default();
f();
f();
}
fn inner3<F: Default + Fn()>() {
let f = F::default();
let g = F::default();
f();
g();
}
fn inner4<F: Default + Fn()>() {
let f = F::default();
F::default()();
f();
}
inner1::<F>
}
Because really, a trait bound of F: Default + Fn() is asking for a type with two operations: fn() -> F {<F as Default>::default} and fn(&F) {<F as Fn()>::call}, when we mostly just needed the single operation fn() {<F as FnStatic()>::call_static} which doesn't even need to manifest a value of F.
Well, one could always use FnOnce() + Default. I suppose a practical disadvantage is that one does not implicitly get extra bounds like Send + Sync + Clone + Copy with just Default alone, but I don’t know how relevant this is. Maybe a FnItem trait could have Send + Sync + Clone + Copy + Default + Fn as supertraits.
Another consideration of the FnItem design is that we’d have to have AsyncFnItem as well – not terrible but certainly an increase in standard library API surface.
The one thing that does bother me about <F>() is that it’s a whole new syntax for a relatively niche feature. I would guess that most intermediates would not know it, and coming across it in a codebase is hard to use a search engine for. On the other hand, even though F::default()()looks weird, it’s perfectly predictable.
The other options don’t seem great – F::call conflicts with Fn::call, and F::call_static uses the already too-overloaded term “static”  – but F::call_item is searchable. It does seem weird that only call_item and not call_mut or call_once are exposed, and that it’ll be the only method call in the language to support variadics, but functions already have special syntax and this is at least predictable.
It's perhaps not the most elegant syntax at the call site, and it'll require a way to encode function items into valtrees (for the generic monomorphization), but it resolves the "doesn't define a name in the value namespace" issue fairly directly.
But I also think the issue isn't that big of a deal, nor is the awkward F::default()() calling syntax, as I expect the most common way to use it would actually still provide a (zero sized) value, e.g.:
"All" this needs is a rule that function items implement Default and a rule that closures that only capture (zero or more) 1-ZST types that implement Default also implement Default and can be coerced into function pointers.
Function items currently are nameable only in the value namespace and not the type namespace, anyway.