Using an enum variant as a Fn impl

Looking at code in iced I see this

enum Message {
      ......
    ActionPerformed(text_editor::Action),
}

and

            text_editor(&self.content)
                .height(Fill)
                .on_action(Message::ActionPerformed)

where on_action is

    pub fn on_action(
        mut self,
        on_edit: impl Fn(Action) -> Message + 'a,
    ) -> Self {
        self.on_edit = Some(Box::new(on_edit));
        self
    }

my reading of that fn is that it wants an invokable function.

The code works the same if I do

.on_action(|action| Message::ActionPerformed(action))

which is what I would expect to have to do.

I know that if x were a function (with the correct signature I could just do

.on_action(x)

but it is a surprise to be able to do it with an enum variant

Am I reading it correctly that anything that can be rendered x(y) can be used in place of a Fn requirement? Or is it special to enum?

Seems its maybe like c++ functors - if its syntactically valid to go x(y) then it can be used where a callable function would work.

1 Like

a tuple like enum variant can be instantiated with a call expression. most common one is Ok, Err, Some, etc.

https://doc.rust-lang.org/stable/reference/items/enumerations.html#r-items.enum.tuple-expr

Tuple structs can do this too. Technically there's a Foo in the type namespace and a notional fn Foo(s: String) -> Foo in the value namespace.[1]

You can't do it with a macro fragment,[2] so I guess not anything. But probably most things you think of.

(Also a type may meet a FnOnce or FnMut requirement but not a Fn requirement, etc.)


  1. Unit structs have a notional constant in the value namespace ↩︎

  2. foo(foo!) ↩︎

A better counterexample, perhaps, is that this will be interpreted as an attemped field access, not a uninvoked method.

foo(var.method)

And here's another silly example, and yeah I guess there's probably other exceptions.

so its just like c++ functor - if its syntactically valid to do x(y) (with y of type T) then it can be used as an impl Fn(T).

Another counterexample is that there are unsafe fns which can be called but there is no corresponding UnsafeFn trait (though there are unsafe fn-pointer types).

2 Likes

it works at type level, not the syntax level. not all "syntactically callable" types implements the Fn() trait. Fn() is a special trait implemented by the compiler automatically for specific types. these types include safe functions, safe function pointers, and closures. specifically, unsafe functions and function pointers are not included, as mentioned by @kpreid.

the reason it works for tuple structs (and tuple enum variants) is NOT because the syntax to construct a tuple struct looks like function call syntax, but it IS actually a function call.

in rust, types and functions have different namespaces. a tuple struct definition defines both a type, and a function with the same name -- its constructor.

https://doc.rust-lang.org/stable/reference/items/structs.html#r-items.struct.tuple

note, you can use function call syntax on a value doesn't imply its type implements Fn(T), it may implements just FnMut(T), or FnOnce(T).

also, if by "syntactically valid", you also include method calls, then there are even more nuances. here's a peculiar example. (fields and methods can have same names too)

fn call(f: impl FnOnce(i32), x: i32) {
    f(x);
}

call(foo.bar, 42);
foo.bar(42);
(foo.bar)(42);

the struct Foo is defined like this:

struct Foo {
    bar: fn(i32),
}
impl Foo {
    fn bar(&self, _: i32) {
        println!("called method bar")
    }
}

let foo = Foo {
    bar: |_: i32| println!("called field bar"),
};
5 Likes