This exposes the function type of a function item to the user.
Motivation
DasLixou
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.
Then after I heard about axum's #[debug_handler] 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.
For that, I would need to mark the type behind the function item, for example, with a trait.
madsmtm
In Swift, some functions have an associated selector that you can access with #selector.
In my crate objc2, it would be immensely beautiful (and useful) to be able to do something similar, e.g. access a function's selector using something like MyClass::my_function::Selector or selector(MyClass::my_function), instead of having to know the selector name (which might be something completely different than the function name).
Terminology
I'll may shorten function to fn sometimes.
- function pointer: pointer type with the type syntax
fn(?) -> ?directly pointing at a function, not the type implementing theFn[Once/Mut](?) -> ?traits. - function item (or just function): a declared function in code. free-standing or associated to a type.
- function group: many non-specific functions with the same signature (params, return type, etc.)
- function trait(s): the
Fn[Once/Mut](?) -> ?traits - function type: the type behind a function, which also implements the function traits.
- fixed type: directly named type, no generic /
impl Trait. - describe the function type: write
fn(..) -> ? nameinstead of justfn name.
Guide-level explanation
As we all know, you can refer to a struct by its name and for example implement a trait
;
When we want to target a specific function for a trait implementation, we somehow need to get to the type behind it. Refering to the hidden type is achieved via the following syntax
For function signatures, where every parameter/return-type is a fixed type and can be known just by refering to the function (so no generics or impl Trait parameters/return type), we can drop the redundant information:
💡 NOTE: Even when we need to describe the function type but the return type is
(), we can (just as for function pointers and function traits) drop the-> ()from the type. (This should also be added as a lint).
A function with a more complex signature, like a function that specifies const, unsafe or extern "ABI", we just ignore that when naming the type:
const
// or with explicit declaration
When having an async function, we in theory have a impl Future<Output = ..> as a return type, which should force us to explicitly declare the function type like so
async
We can take a shortcut and use the async keyword, as long as the Output assoc type in the Future is still fixed
async
Reference-level explanation
As described in the Guide-level explanation, with the syntax [async] fn[(..) -> ?] <fn_path>, we can reference the type behind the named function.
When the function is for example in a different mod, it should be referenced by its path
⚠️ NOTE: The same rules apply here as for normal types. Either the function item or the trait to implement mustn't be foreign for the impl. Same as E0210
It should be also possible to get the type of associated functions:
;
When the associated function comes from a trait, the same rules as for associated types apply here (Ambiguous Associated Type, E0223):
;
type MyTrait
// ERROR: ambiguous associated function
// instead:
// OK
When the type of the associated function has generics, they will be handles as follows
;
// or fully described:
When a function has generics, the function type is forced to be described, and the generic should be placed at it's desired position:
When we have an implicit generic, the same rule applies
When functions have lifetimes, they have to be included in the types
When the lifetime is explicitly defined on the function signature and there's no other rule forcing us to describe the function type, we can take a shortcut as follows
// explicit lifetime 'a
Just as structs and enums have the possibility to derive traits to automatically generate code, function type have similar ways via attribute macros:
// Expands to
Other than that, it should behave like every other type does.
Additional ToDo's
Change the fn type syntax for consistency
When we try to compile the current code snippet
we get following error:
error[E0308]: mismatched types
--> src/main.rs:6:18
|
6 | let _a: () = cool;
| -- ^^^^ expected `()`, found fn item
| |
| expected due to this
|
= note: expected unit type `()`
found fn item `for<'a> fn(&'a _) -> (i32, bool) {cool::<_>}`
For more information about this error, try `rustc --explain E0308`.
For consistency, we should change the syntax to for<'a, T: Clone> fn(&'a T) -> (i32, bool) cool (I'm not sure if we should put generics in the for)
Drawbacks
Rationale and alternatives
The type behind functions already exists, we just need to expose it to the user.
Prior art
i dont know any
Unresolved questions
- Is the syntax good? It could create confusion between a function pointer.
- 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
- Also expose the type of closures