Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: expose-fn-type #3476

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
120 changes: 120 additions & 0 deletions text/3476-impl-trait-for-fn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
- Feature Name: `impl-trait-for-fn`
- Start Date: 2023-08-20
- RFC PR: [rust-lang/rfcs#3476](https://github.com/rust-lang/rfcs/pull/3476)
- Rust Issue: N/A

# Summary
[summary]: #summary

Support for implementing traits on functions

# Motivation
[motivation]: #motivation

I was trying to make something similar to bevy's system functions. And for safety reasons, they check for conflicts between SystemParams, so that a function requiring `Res<A>` and `ResMut<A>` [panic](https://github.com/bevyengine/bevy/blob/main/crates/bevy_ecs/src/system/system_param.rs#L421).

Then after I heard about axum's [`#[debug_handler]`](https://docs.rs/axum/latest/axum/attr.debug_handler.html) I wanted to do something similar to my copy of bevy systems, so that I get compile time errors when there is a conflict. I wanted even more, I wanted to force the user to mark the function with a specific proc attribute macro in order to make it possible to pass it into my code and call itself a system.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

As we all know, you can implement a trait for a struct with the following syntax
```rust
struct Timmy;
impl Person for Timmy {
fn greet() {
println!("Hey it's me, Timmy!");
}
}
```
And we can also implement a trait for all functions with a specific signature like this
```rust
impl<F> ValidSignature for F
where F: Fn(i32) -> bool
{
/* ... */
}
```
Now we can also implement traits for specific functions only
```rust
fn valid() {}
impl ValidFunction for fn valid {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a lot more elaboration on what it actually means. impl accepts types. Does this mean that fn valid is now a type referring to the fndef ZST? Can it be used anywhere?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what do you mean by fndef ZST?
And yes, as to my knowledge this is "makes it" a type just as the implementation of Fn also requires it to be.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every function already has an implicit type. So I assume that what you mean is that fn x is syntax to refer to the type of x. The RFC should elaborate that, instead of just being around trait impls.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every function already has an implicit type. So I assume that what you mean is that fn x is syntax to refer to the type of x. The RFC should elaborate that, instead of just being around trait impls.

Oh sorry 😅 yes thats exactly what I want. Also: in the unresolved questions tab i wasnt sure if we could drop the fn in the impl block. But I think it is unique, right? So what do you think about dropping the fn?
Also: when you say elaborating to the type and not just about trait impls, another question came to my mind: Do we maybe also want to impl directly on a function (I currently work on smth that would really benefit from such a thing)?
Then together with a dropped fn this would look smth like

fn itoa(val: i32) -> String { .. }
impl itoa { .. }
// or without dropping fn
impl fn itoa { .. }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nilstrieb can this be marked as resolved?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically what I'm saying is that "allow impls for function definition types" is the wrong way to approach this. The real feature here is being able to refer to function definition types, so the RFC should focus on that. Mentioning that function definition types are considered local for coherence and can therefore be used in trait impls is then just a small sidenote.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nilstrieb ohhh... thats a great point but I'm not sure if i can rewrite it for it that much because the motivation / goal is more on trait impls directly. But ill try to make it focus more on this point.
Thanks for the feedback 😄

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nilstrieb may you recheck the RFC? I rewrote it but i'm unsure if it's still too focused on the impl thing 😅

/* ... */
}
```

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

It gives the possibility to also implement a trait directly scoped to a function instead of generic implementation of multiple. Other than that, it basically behaves the same.


When the function has parameters, modifiers or a return type, it should not be included in the impl block, because the path is already unique
```rust
async fn request_name(id: PersonID) -> String { .. }

impl Requestable for fn request_name {
/* ... */
}
```
When the function is for example in a different mod, it should be referenced by its path
```rust
mod sub {
fn sub_mod_fn() { .. }
}
impl OutOfNamesRunnable for fn sub::sub_mod_fn {
/* ... */
}
```
Just like how a fn inside an impl block still implements the Fn trait, it should also be possible to implement traits for them
```rust
struct MyStruct;
impl MyStruct {
fn new() -> Self { Self }
}
impl Creatable for fn MyStruct::new {
/* ... */
}
```
It should also be possible to implement them via proc attribute macros:
```rust
#[impl_debug_name = "Greeting"]
fn greet() {
/* ... */
}
// should expand to something like
fn greet() {
/* ... */
}
impl FnDebugName for fn greet {
fn debug_name() -> &'static str {
"Greeting"
}
}
```

# Drawbacks
[drawbacks]: #drawbacks

i dont know any

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

I think it's a easy task because we can already implement traits for a group of functions with the same signature, so why shouldn't we also implement single scoped impls?

# Prior art
[prior-art]: #prior-art

i dont know any

# Unresolved questions
[unresolved-questions]: #unresolved-questions

- Is the syntax good? I feel like we could drop the `fn` to from `impl Trait for fn function` to `impl Trait for function`.
DasLixou marked this conversation as resolved.
Show resolved Hide resolved
- What about closures? They don't even have names so targetting them would be quite difficult. I wouldn't want to use the compiler generated mess of a name like `[closure@src/main.rs:13:18: 13:20]`. It would also contain line numbers which would be changing quite often so thats not ideal.

# Future possibilities
[future-possibilities]: #future-possibilities

- also make it possible to implement traits for closures directly.