Closure syntax for non-function traits

As impl Trait is getting closer to stabilization, I have been wondering about some syntactic sugaring. Perhaps this has been discussed before. If so, please let me know where.

Current state

On current nightly with #[feature(conservative_impl_trait)] enabled, if I want to write a function that returns an unboxed closure, I can do this:

fn add_number(a: i32) -> impl Fn(i32) -> i32 {
    move |b| a + b
}

This is nice, I can provide an implementation without going through all the trouble of (1) defining a new type, (2) implementing the trait for that type and (3) instantiating the type in my function. It all happens in one go.

However, for any other trait, I still have to do all these things:

trait Foo {
    fn foo(&self);
}

struct Bar;

impl Foo for Bar {
    fn foo(&self) {
        println!("the bar way of doing foo!");
    }
}

fn some_foo() -> impl Foo {
    Bar
}

This is still quite a lot of boilerplate, if all I want is to return the fact that the println!(...) is to be executed as an implementation of Foo.

Suggestion

Could we not have something like the following instead?

trait Foo {
    fn foo(&self);
}

fn some_foo() -> impl Foo {
    || println!("the bar way of doing foo")
}

I am not quite sure, what to do with traits that have more than one method syntax-wise. One option would be something like this:

trait Foo {
    fn foo(&self);
    fn bar(self);
}

fn some_foo<'a>(a: i32, b: &'a i32) -> impl Foo + 'a {
    impl {
        foo: || println!("I am an Fn closure: {}", &b),
        bar: move || println!("I am an FnOnce closure: {}", a)
    }
}

Ownership seems something to think about, especially as the different closures should actually be able to share the captured state among them, so the whole block just captures all the context once.

I guess the syntax is debatable, it is just the first way of writing it down that came to my mind.

It would make e.g. implementing your own iterables a lot shorter to write.

Do you see any merit in pursuing this idea further? Or is it not worth the extra complexity?

@sfackler has a draft RFC on this topic!

Can’t you get what you’re going for in this example though by implementing Foo for all Fn() types? Then the closure will be a type that implements Foo.

E.g.

impl<F> Foo for F where F: Fn() {
    fn foo(&self) { self() }
}
2 Likes

My draft is here: https://github.com/sfackler/rfcs/blob/functional-closure/text/0000-functional-closure.md. I should update it and make a PR…

@withoutboats the motivation section covers some cases where the blanket impl approach isn’t sufficient.

1 Like

@sfackler nice, I knew I could not be the only person thinking about this.

I admit that Java 8 was also an inspiration for me. Usually I dislike that language, but that one feature actually makes sense. Thinking of it, Java actually started out with ad-hoc class instances implementing interfaces and only later used closures as syntactic sugar for the one-method case. (That earlier syntax was pretty verbose though as it was essentially an entire class definition without fields and constructor.)

Maybe the multi-method thing is a bit far-fetched for now. But it would be nice to keep that door open at least.

I personally think it would be preferable to have general-case ‘object literals’ for any object-safe trait, rather than restricting it to “functional” traits.

2 Likes

@glaebhoerl You are talking about dynamically dispatched trait objects instead of the anonymous impl Trait-style literals?

Both would be useful imho. I would still go for the statically-typed version, because once that is done, you can easily put it in a box.

Looking at this these two suggestions are orthogonal and could be dealt with in separate RFCs. One similar to @sfackler’s functional closures, and another introducing object literals or perhaps trait literals would be a more suitable name, if we indeed go for the static variant.

I would be willing to write the second one up, but it would be nice to have people that could provide some feedback on syntax and implementation implications (I don’t work on the compiler).

Yes, that's what I meant I think. Just like the current function literals (closures), introducing an anonymous type and an impl etc., except with different traits.

Along the lines of more general “anonymous type” syntax, we can borrow further from Java:

SomeInterface foo = new SomeInterface() {
    void method1() {}
    void method2() {}
};

in Rust, something like

let foo = impl SomeTrait {
    fn method1(&self) {}
    fn method2(&self) {}
};

The lambda syntax added in Java 8 removes a lot of the use of this anonymous class declaration form, but it’s still used in cases where you need to implement multiple methods. I think it’d be cool to support as well in Rust, though in this case we’d need to bikeshed what the syntax would be :D.

1 Like

Something like that, yes. However, I am a bit wary of using syntax that looks that much like an actual implementation block. Somehow I have learned that fn implies a function that does not close over its environment. It would be a bit surprising to be able to refer to the environment using this new construct, but not in a plain inline function.

And perhaps more importantly I don’t think it fits the semantics here. After all, what is self supposed to be? The variables that are closed over are determined implicitly, so having an explicit self parameter seems like it is merely decoration in this context.

That’s why I was drawn towards the struct of closures-like syntax in the opening post. Closures are already designed for this kind of interaction with the environment.

Adding the name of the trait to the syntax is a good idea.

I wonder, whether it is perhaps possible to implement this as a syntax extension for prototyping. That would help to experiment with the feature a little.

2 Likes

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