Suggestion: Implement Default for function items

Problem

It is currently not possible to obtain a function value from its function item type, despite the fact that every function has a unique function item type (note: not to be confused with function pointer type). This should be made possible, since it allows us to compose functions and store the result as function pointers instead of resorting to boxed trait objects.

Example to illustrate (simplified from my actual use-case):

// We want to "decorate" a function by preprocessing its arguments before calling it, i.e. given 
// some f(x), return a function representing f(g(x)). Afterwards, we want to dynamically dispatch
// the decorated function.

// Version 1:
// Here, we are forced to use `Fn` bound, even though we only want non-closure functions.
fn make_decorated<F: Fn(SomeType)>() -> fn(SomeType) {
    decorated::<F>
}
fn decorated<F: Fn(SomeType)>(x: SomeType) {
    let x = x.changed_version();
    // f(some_arg) // Can't get function item `f` from `F`
}


// Version 2: Forced to use boxed trait object to get around the limitation.
fn make_decorated_2<F: Fn(SomeType)>(f: F) -> Box<dyn Fn(SomeType)> {
    // Forced to use closure
    let decorated = |x: SomeType| {
        let x = x.changed_version();
        f(x)
    };
    Box::new(decorated)
}


// Version 3: Returns impl Fn(SomeType) but not what we want.
fn make_decorated_3<F: Fn(SomeType)>(f: F) -> impl Fn(SomeType) {
    // Forced to use closure
    let decorated = |x: SomeType| {
        let x = x.changed_version();
        f(x)
    };
    decorated
}

Proposal and Alternatives

There are several solutions I can think of.

  1. Introduce a new trait NoEnvFn which only normal non-closure functions implement, and a corresponding method NoEnvFn::instance() to obtain the instance.
  2. Implement Default for normal functions. In the example above, we can change the required trait bound to F: Fn() + Default. This is more general than (1), allowing more than normal functions to be accepted when custom types can implement Fn stably, but does not require introducing a new trait. This is the proposal in this thread.

Complications

I'm not familiar with the internals of the compiler but from what I gathered, Default is not a language-item, which might pose issues to the automatically generated function item types.


The issue here might be niche but we don't want to be forced to pay for what we don't use, sticking to our beloved zero-cost abstraction principle.

What do you think?

Have you seen Add design notes for function-type `Default` implementation discussion by Diggsey · Pull Request #71 · rust-lang/lang-team · GitHub? (there is some additional discussion at https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/design.20note.20lang-team.2371)

Why isn't this acceptable? Is the problem just that it's hard to store?

FWIW, I ran into a similar situation in a project of my own recently. My workaround was to make a trait with a method that doesn't take self, and implement that trait rather than writing free-standing functions. Given any implementor, you can make a second implementor that decorates it – and you can convert the method to a function pointer by specifying the generic parameter.

Making function pointer objects more full-featured would be nice, but this workaround worked well enough for my personal use case.

1 Like

There's another fun world where the solution is to lift the fn() parameter into a const generic. I think that's the most direct translation of what you're after.

I think what you think you want is fundamentally flawed, though. You say you want to return a decorated fn() function pointer, that does dynamic dispatch for the decorated function.

Fundamentally, that doesn't work out. fn() has no state (by design), so it statically wraps the decorated function ZST. Think Decorated<{fn foo}>: Fn(); you have a distinct instance for each wrapped function.

So either you return some impl Fn which has the pointer state to do dynamic dispatch, or you have statically monomorphized decorators which require zero state (just emitted code duplication) and do static dispatch.

But I do agree that there's a fundamental missing piece of vocabulary missing for a "&dyn Fn() but thin pointer, no state", or rather, fn(), that allows composing wrappers but getting the thin pointer out at the end with unifiable dynamic type. And impl Fn + Default does seem to be enough to express it, plus a bit more.

In the mean time, you might try picking out a crate that'll give you StackBox<dyn Fn()>. It'll necessarily be a bit bigger than fn() would be, but it'll accomplish what you want.

1 Like

Thanks for the link. Somehow I didn't encounter this issue on search engines.

Imagine if I want to store a collection of different decorated functions which have the same signature. It's not possible to use impl Fn(SomeType) in that way directly except for statically dispatching it.

That sounds interesting. I thought to somehow use traits too but I didn't want to burden the consumer of my library to implement a trait for every freestanding function they give.

It seems that they'll have to create a dummy type so that they could do impl Trait for Dummy. I wonder if there is a way around that boilerplate?

Box<dyn Fn()> alone will fit my needs, considering the fact that function item types are ZST so I don't think any actual allocation will happen. I'm mostly trying to avoid the additional size and indirection of dyn Fn(), which may be costly as this is likely going to be one of the hottest loop in my code.

The most brute-force way around boilerplate is a macro. If you're willing to do a bunch of work as the library author, you could make a proc macro that the user can just apply as an attribute to their function, which would leave the function unmodified but also make a dummy type and implement the trait for the dummy type in terms of the function.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.