rfcs
rfcs copied to clipboard
RFC: `expose-fn-type`
Im unsure about fns of impl blocks. Should this be allowed?:
struct MyStruct;
impl MyStruct {
fn new() -> Self { Self }
}
impl Creatable for fn MyStruct::new {
/* ... */
}
I have to check whether this applies to the function traits. When the fn new
implements Fn()
(I think it is, I just have to make sure), then we probably also want the possibility to implement directly on them, right?
Edit: Checked, those fns still implement Fn trait (as I thought, no reason why not), so I also added an example of how to implement traits for those.
Fn
, FnMut
, or FnOnce
are closures, not functions. Ain't clear how you'd name the type of a closure, but in theory there is a type there which could satisfy extra traits. And the compiler does them sometimes.
A type like fn(..) -> ..
is a function pointer type. I think impl Trait for fn(..) -> ..
covers all function with that signature. I doubt impl Trait for fn foo
could make sense, because fn bar(..) -> .. { ... }
is a value of this function pointer type, not a type itself.
Just fyi #![feature(fn_traits)]
permit creating closures manually, but one should typically forgo the function call syntax, and just define the trait you desire.
Fn
,FnMut
, orFnOnce
are closures, not functions. Ain't clear how you'd name the type of a closure, but in theory there is a type there which could satisfy extra traits. And the compiler does them sometimes.A type like
fn(..) -> ..
is a function pointer type. I thinkimpl Trait for fn(..) -> ..
covers all function with that signature. I doubtimpl Trait for fn foo
could make sense, becausefn bar(..) -> .. { ... }
is a value of this function pointer type, not a type itself.Just fyi
#![feature(fn_traits)]
permit creating closures manually, but one should typically forgo the function call syntax, and just define the trait you desire.
I'm aware of that. Fn traits can focus on closures, but also target normal functions. Also this RFC is for implementing a trait on a function directly because something like impl<F: Fn(i32) -> bool> MyTrait for F
is already possible but not for a single function only.
Pointing at closures is also a thing I mentioned in the "Unresolved Questions" chapter.
You cannot impl Trait for value
anywhere in Rust, so doing exactly this sounds impossible.
You should ask if const generics could achieve similar, roughly like:
type F = fn(..) -> ..;
const GOOD_FN: [F] = [ foo, bar, baz ];
impl Trait for F
where GOOD_FN.contains(self)
{
}
I've no idea if this makes sense either, but at least it resembles topics being discussed elsewhere.
There is only one impl Trait for F
of course, and F
is defined elsewhere, so the orphan rules require Trait
be local here, but you could still seal fn
parameters like this.
There are many reasons why one would choose an explicit type over an fn or closure, so then invocations look like MyFn::go(..)
, so you can do whatever you like.
pub trait MyFn {
fn go(..) -> ..;
}
@burdges I think you got confused by the syntax. When writing impl MyTrait for fn x
it doesn't implement MyTrait
for the function pointer, rather for the ghosttype behind the function x
. Hope that clears things up 😅
Afaik there is no ghost type behind the function x
, only behind closures.
@burdges There is a zero sized type for the function itself too.
Afaik there is no ghost type behind the function
x
, only one behind closures.
No that's wrong. Closures and functions can have ghost types.
Here's an example where I use the ghost type behind a function which implements the Fn
trait:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6789b32b05fcf48f39bc123d3f8c5bdf
@burdges
If some ghost type already exist then whatever, not sure why it'd exist but maybe so.
Anyways, that playground link seemingly uses the function pointer type, not some ghost type:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=94e692e9d42ed1ad05f068f4777da009
You could try printing type_name
to get a final answer there.
If some ghost type already exist then whatever, not sure why it'd exist but maybe so.
Anyways, that playground link seemingly uses the function pointer type, not some ghost type:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=94e692e9d42ed1ad05f068f4777da009
You could try printing
type_name
to get a final answer there.
interesting approach...
@burdges https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=933d56be52fa879eabe2816c1f7da7ce this looks like a ghost type
Cool. Yes it seems to show a ghost type, not sure why that exists, but maybe it helps in inlining?
If you add the line not_really_a_ghost(this_has_a_ghost_type);
then you print fn()
which is what should happen.
The comment is incorrect though: Fn
types are intentionally implemented for function pointers. The surprising thing is that they're also implemented for these ghost types, but maybe this improves inlining or something.
See https://doc.rust-lang.org/reference/types/function-item.html#function-item-types
So I added an example for functions with generics, but came across the problem with impl Trait
types.
We could forbid exposing the type of such functions for now but we'll have to get to it later. Particularly when we have derive macros they should be capable of handing functions with impl Trait
params/return types.
Or we quickly find a good syntax for it (i just crumbled something up in the RFC where we just add the impl Trait
generics in order after the other ones, but because we also don't have a syntax for such things with other types, which I think is also the reason why
struct MyStruct {
val: impl MyTrait
}
isn't allowed, i don't think we want this to be a quick decision.
Thoughts?
So, historically, I've desired the ability to directly name the types of individual functions before. However, I eventually conceded that type-alias-impl-trait was the solution for this feature, since it seems unclear what the benefit would be.
Here, the main use case presented is implementing traits for functions, which… I dunno, I don't really like that. To me, it feels like we should have the inverse of that: allow implementing functions (i.e., FnMut
, Fn
, and FnOnce
) for other kinds of objects. To me, having to implement a trait for a function feels like a hack because other kinds of objects can't be functions.
@clarfonthey so I kind of understand your arguments and I totally agree that we also want the user to be able to implement Fn traits on his own objects, but I think it would be the best to have both. I am also currently writing a library where I would need this and having the user to make a zero-typed struct and implementing the Fn trait + my trait vs. Just using a function makes really a difference.
@clarfonthey wait... how does type-alias-impl-trat have anything to do with this RFC?
@clarfonthey wait... how does type-alias-impl-trat have anything to do with this RFC?
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f704eaba39577e9f9321782fa84afdf5
#![feature(type_alias_impl_trait)]
use std::iter::Map;
type MyFn = impl FnMut(u32) -> u32;
fn square(val: u32) -> u32 {
val * val
}
fn squaring<I: Iterator<Item = u32>>(iter: I) -> Map<I, MyFn> {
iter.map(square)
}
@clarfonthey so I kind of understand your arguments and I totally agree that we also want the user to be able to implement Fn traits on his own objects, but I think it would be the best to have both. I am also currently writing a library where I would need this and having the user to make a zero-typed struct and implementing the Fn trait + my trait vs. Just using a function makes really a difference.
Note that an alternative to implementing the Fn
trait + your trait is to simply add the method they would put as the actual function, as a method on your trait. That's the standard way people have been getting around the lack of this feature, and personally, I find it less confusing than implementing a trait for a function.
Being able to reference the type for a function would be useful if type-alias-impl-trait couldn't do that, but it can, and it's going to be one of the primary uses of it. Compare the above signature to:
fn squaring<I: Iterator<Item = u32>(iter: I) -> Map<I, fn square>;
It's potentially cleaner, but not really enough to warrant the existence of the feature and new syntax, IMHO.
@clarfonthey yeah. In that scenario type-alias-impl-trait can represent it and I would also use it over directly using the function. But that's just saying "a part of what this RFC makes possible is already possible so we don't need this". And for some people (like me) it would be really great to get to the type behind a function. If you have concerns over the RFC that it would break something or have feedback, please let me know.
I mean, that's not really how the RFC process works. These features take time and energy to implement, and it's extremely unclear why that energy would be spent toward implementing this feature instead of:
- Using type-alias-impl-trait in case you need to directly reference the type for a function (since that feature is actively being developed and would certainly be finished before this feature were even started)
- Adding extra methods to traits instead of implementing those traits with fewer methods for functions
Like, sure, this feature as-is wouldn't actually conflict with a lot. But that's only a small bit of the RFC process: the rest is making sure that it fits in with the rest of the language, and is worth implementing. I don't really see many arguments in favour of that.
Semantics aside (and I do like the idea of being able to name function types), rather than having a function-specific syntax like fn
, this seems like a good argument for having a general type_of!
, that works on functions and on anything else.
A type_of!
macro would also help in turning generics into a variadic function, for size optimizations. I have an open issue (https://github.com/rust-lang/rust/issues/114598) about this, but it's lacking exactly that.
@joshtriplett I also thought of something like type_of keyword instead of fn but didn't know that there is already planning (I guess) for such a macro.
Is this useful? It maybe useful to have traits that declare invariants, like
let my_closure = |..| ..;
unsafe impl SafeforFoo for type_of!(my_closure) { }
something_that_requires_foo(my_closure)
It'd work if my_closure
was a bare function too, but would it still be useful if that were the only option? I'd think way less useful than for closures..
I'd wager #![feature(fn_traits)]
winds up much more heavily used though, not quite the same as being more useful.
@burdges both fn_traits and your example seem quite promising! I totally forgot that you can write an impl everywhere, so that would also eliminate the closure problem. Also: rust has a type
keyword reserved, does it have a usecase? Could we write impl SafeForFoo for type my_closure {}
?
Edit: I am stupid... type aliasing lol
@orowith2os does there already exist a RFC for type_of?
Could we write
impl SafeForFoo for type my_closure {}
?
afaict that should work, we can borrow C's sizeof
syntax and make type <identifier>
and type(<expression>)
work as type-of.
Could we write
impl SafeForFoo for type my_closure {}
?afaict that should work, we can borrow C's
sizeof
syntax and maketype <identifier>
andtype(<expression>)
work as type-of.
Now the question is what we understand as an
Could we write
impl SafeForFoo for type my_closure {}
?afaict that should work, we can borrow C's
sizeof
syntax and maketype <identifier>
andtype(<expression>)
work as type-of.Now the question is what we understand as an . A variable name would be an identifier as well as an expression
yes, that simply means both type my_var
and type(my_var)
(and type((my_var))
and so on) are all valid. but type a * b
would not be since it's missing parenthesis. this is similar to const generic arguments f::<A>
or f::<{ A * B }>