Tracking issue for Fn traits (unboxed_closures & fn_traits feature)
Tracks stabilization for the Fn* traits.
Random bugs:
- [ ] https://github.com/rust-lang/rust/issues/42736 --
foo()sugar doesn't work where you have&Foo: FnOnce - [x] https://github.com/rust-lang/rust/issues/45510 -- trait-based dispatch not working
Tracks stabilization for the
Fn*traits.Random bugs:
- [x] https://github.com/rust-lang/rust/issues/45510 – type-based dispatch not working
- [ ] https://github.com/rust-lang/rust/issues/42736 –
foo()sugar doesn't work where you have&Foo: FnOnce
Alexander Regueiro at 2019-07-02 03:19:54
Inability to delegate calls to other FnOnce implementors like this:
struct A<T>(T); impl<T, Args> FnOnce<Args> for A<T> where T: FnOnce<Args> { type Output = <T as FnOnce<Args>>::Output; fn call_once(self, args: Args) -> Self::Output { FnOnce::call_once(self.0, args) } }is the reason I have a safety issue in libloading.
Simonas Kazlauskas at 2016-05-13 17:18:03
FnOnce::Outputis stabilized in https://github.com/rust-lang/rust/pull/34365Vadim Petrochenkov at 2016-07-12 12:25:09
Could someone summarise what is blocking stabilisation here please?
Nick Cameron at 2016-08-17 22:43:44
@nrc uncertainty about
Argsbeing the right thing given the possibility of variadic generics coming along around 2030 is the most major reason this is unstable.Simonas Kazlauskas at 2016-08-18 00:00:55
@nrc also possibly some questions around the inheritance relationship, see https://github.com/rust-lang/rust/issues/19032
Aaron Turon at 2016-08-27 17:24:05
There should be a way of casting
fn(A) -> BtoFn(A) -> Bbrunoczim at 2018-01-18 15:44:51
@brunoczim That coercion already happens implicitly, but
Fn()as a type is a trait object so it needs to be behind some kind of pointer:use std::rc::Rc; fn main() { let _: &Fn() = &main; let _: Box<Fn()> = Box::new(main); let _: Rc<Fn()> = Rc::new(main); }Simon Sapin at 2018-01-18 18:37:47
One issue we've been discussing on https://github.com/TheDan64/inkwell/issues/5 is the ability to mark something as
unsafeto use. What about having anUnsafeFnmarker trait which could be used to tell the compiler a callable isunsafeto call?For context,
inkwellis a wrapper around LLVM and we're trying to figure out how to return a function pointer to a JIT compiled function, when calling the function is fundamentallyunsafefor the same reasons FFI code isunsafeto call.Michael Bryan at 2018-04-02 08:17:30
There is another reason to add
UnsafeFn: currentlyunsafe fns don't implementFn. There is no way to passunsafe fnas a function argument, they are second class citizen: example.UnsafeFncould be implemented for things implementingFn, soFns can be used whereverUnsafeFnis required. There probably also should beUnsafeFnMutandUnsafeFnOnce.Igor Żuk at 2018-05-04 19:11:19
@Michael-F-Bryan @CodeSandwich This sounds like something for which an RFC would really be appreciated. It probably wouldn't be an overly long or intricate one to write, even. I would support it, for sure, and judging by an issue I saw about this not long ago (a long-standing one), others would too.
Alexander Regueiro at 2018-08-20 00:47:37
@alexreg Ok, I'll prepare it in spare time. Unfortunately I lack knowledge and experience to actually implement it or even fully understand the idea.
Igor Żuk at 2018-08-21 22:54:00
@CodeSandwich Don't worry, so do I! Maybe get yourself on Rust's Discord (#design channel) and we can discuss it with some people who really know the nitty gritty? You can ping me there, same username.
Alexander Regueiro at 2018-08-21 23:45:11
With every unsafe function comes a manually written "contract" saying when that function may or may not be called.
With
UnsafeFn, who is setting that contract?Ralf Jung at 2018-08-22 19:27:35
With
UnsafeFn, who is setting that contract?I'd say this is done on a case-by-case basis. It's really hard to specify the various invariants and assumptions you make in
unsafecode in something as rigid as a type system, so anything that accepts anUnsafeFnwould probably also need to document its assumptions.Before writing up a RFC I thought I'd make a post on the internal forum. It'd be nice to hear what opinions other people have on this topic and the different solutions they come up with.
Michael Bryan at 2018-08-23 12:37:36
I don't know how one would realistically implement this, but probably the ideal semantics is to have any function that takes an
unsafefunction as an argument to also beunsafe.Eira Fransham at 2018-11-05 14:59:09
A different interface/API likely requires a separate set of traits. Though multiplying the number of traits doesn’t sound great, especially if we later want to also support
const fnclosures (andunsafe const fn?).Simon Sapin at 2018-11-05 15:26:23
Is there a way to implement these as wrapper traits?
Unsafe<T> where T : FnOnce,Const<T> where T : FnOnce,Async<T> where T : FnOnceand so on?✨ Audrey ✨ at 2018-11-06 01:43:44
UnsafeFnsounds like an unsound abstraction. When using anunsafefunction, it's your responsibility to read documentation and understand all assumptions and invariants that need to be satisfied. This cannot be abstracted away because those invariants and assumptions are individual to the function in question.Personally, I think
unsafefunctions should just not implement any of theFntraits. It's up to the caller to wrap theunsafefunction in another function in order to pass it around. Unsafe code doesn't need to meet the same standards of ergonomics as safe code, provided that it's still possible to get optimal performance.For example, CodeSandwich's example can be "fixed" using a closure: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=308c3ee61419427c617879fc4cb82738
Peter Hall at 2019-01-10 13:27:57
I agree.
UnsafeFnis meaningless and breaks the mechanics of unsafe proof obligations.Alexis Hunt at 2019-01-14 10:59:11
I understand the argument against
Unsafe Fn. But wouldn't this apply tounsafe fnpointers too?brunoczim at 2019-02-13 18:13:29
I understand the argument against
Unsafe Fn. But wouldn't this apply tounsafe fnpointers too?Yes, I would say so.
Peter Hall at 2019-02-13 18:23:24
what does the difference between Fn and fn?
MagicSource at 2019-07-02 02:31:02
@jinfagang cc https://doc.rust-lang.org/std/ops/trait.Fn.html
csmoe at 2019-07-02 02:48:30
What's the status of this? There are a number of F-unboxed_closures issues but this tracking issue doesn't appear to be... well, tracking the issue. I'm currently working with a project that requires erased closure types and supports #![no_std] and SmallBox-style tricks don't really help when I'm writing library code that lacks any information about the maximum size of the closure state.
Izzy Swart at 2020-02-05 20:59:25
Is it intentional that the code below compiles? It looks wrong to me, as it appears to move out of a mutable reference.
#![feature(fn_traits)] #![feature(unboxed_closures)] pub struct A {} impl std::ops::FnMut<()> for A { extern "rust-call" fn call_mut(&mut self, args: ()) { self.call_once(args) } } impl std::ops::FnOnce<()> for A { type Output = (); extern "rust-call" fn call_once(self, _args: ()) { } }For comparison, this doesn't compile:
struct A { } impl A { pub fn my_mut(&mut self) { self.my_once() } pub fn my_once(self) { } }rcls at 2021-04-28 06:42:35
@rcls I think that's not calling your
FnOnce, but rather the blanketimpl<F: FnMut> FnOnce for &mut Fthat usescall_mut, so you'll have infinite recursion.Josh Stone at 2021-04-28 06:55:56
We definitely want to stabilize the
Fnfamily of traits at some point, allowing people toimplthem.Marking this as "design-concerns" because we need to determine if we should wait for variadic generics or stabilize the tuple-based
rust-callABI.Josh Triplett at 2021-12-08 18:29:35
Should
Argsbe turned into an associated type to prevent people from using it to implement operator overloading based on argument type?bjorn3 at 2021-12-09 17:15:33
no
Lokathor at 2021-12-09 17:42:10
@bjorn3 wouldn't that break higher-order signatures (which are implemented as overloads over the input lifetimes)? FWIW, this question is related to that of the
<details><summary>a silly thing 🙈</summary>Resumeparameter forGenerators.
</details>trait FnOnce<'lifetimes..> { // variadic lifetime-generics? type Args; type Output; extern "rust-call" fn call_once (self, _: Self::Args) -> Self::Output where Self : Sized, ; }Daniel Henry-Mantilla at 2021-12-10 19:10:56
wouldn't that break higher-order signatures (which are implemented as overloads over the input lifetimes)?
Right, didn't think about that.
bjorn3 at 2021-12-10 19:23:13
Silly question, but is there any particular reason why the
Fn*traits actually need to stabilise therust-callABI in order to be implementable?I mean, we already have the custom
Fn(A, B, ...) -> Csyntax as sugar forFn<(A, B,), Output = C>, so, I don't think it'd be unreasonable to adopt a special syntax just for implementing them too.Maybe something like:
struct MyFn(u32); impl MyFn { fn(self, x: u32, y: u32) -> u32 { x + y + self.0 } }Could get desugared to:
struct MyFn(u32); impl FnOnce<(u32, u32)> for MyFn { type Output = u32; extern "rust-call" fn call_once(self, (x, y): (u32, u32)) -> u32 { /* body */ } }Clar Fon at 2022-02-17 03:59:29
@clarfonthey I think there's some needs to implement Fn* traits for arbitrary types.
- Giving multiple function signatures to an object.
- In following URL, there's some difficulty defining trait
Handler, so we want to use Fn* trait directly. https://users.rust-lang.org/t/type-inference-in-closures/78399
yozu at 2022-08-05 12:43:59
Giving multiple function signatures to an object.
This is something I think we shouldn't support in the first place, even with Fn*.
In following URL, there's some difficulty defining trait Handler, so we want to use Fn* trait directly. https://users.rust-lang.org/t/type-inference-in-closures/78399
If I understand it correctly, preventing multiple impls of Fn* would fix this issue.
bjorn3 at 2022-08-05 12:47:04
@bjorn3 Thanks for replying.
we shouldn't support in the first place
preventing multiple impls of Fn*
Do you have any standings for preventing higher-order signaitures as mentioned below? https://github.com/rust-lang/rust/issues/29625#issuecomment-991226468
yozu at 2022-08-06 08:02:44
I don't know how to support higher-order signatures while at the same time preventing multiple call signatures for a type other than keeping Fn* perma-unstable.
bjorn3 at 2022-08-06 09:41:16
I don't know how to support higher-order signatures while at the same time preventing multiple call signatures for a type other than keeping Fn* perma-unstable.
Changing the Fn* traits to make the arguments an associated type could accomplish that. It would break the symmetry with Fn*() constraints though.
Peter
Peter Hall at 2022-08-06 10:51:51
@peterjoel but that wouldn't scale to function-like objects with generic arguments, for which an associated type wouldn't work. What we really want to prevent here are functions with multiple simultaneous argument counts (because varying argument types is already possible, although a bit complicated, using sealed traits), which could be solved with 2 additional traits (one for tuples, one for functions, which binds tuples to the count of directly contained objects, and functions to their argument counts, either via type-level integers, or using const generics + associated constants)
Ellen Emilia Anna Zscheile at 2022-08-06 11:27:15
but that wouldn't scale to function-like objects with generic arguments, for which an associated type wouldn't work.
Closures can't be generic either.
bjorn3 at 2022-08-06 11:55:45
@zseri
What we really want to prevent here are functions with multiple simultaneous argument counts
Is this really harmful? it looks fine in current unstabilized
fn-traits: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=157729b98808b439ecc992e4ba59273eyozu at 2022-08-06 13:28:42
It may be fine from a technical perspective, but from a language perspective we have chosen to not allow function overloading. See for example https://internals.rust-lang.org/t/justification-for-rust-not-supporting-function-overloading-directly/7012, https://users.rust-lang.org/t/is-it-possible-to-specialize-hashmap-index-for-copy-types/7750/5, https://users.rust-lang.org/t/what-is-the-reason-for-not-having-overloaded-versions-of-fn/55208 and https://users.rust-lang.org/t/is-there-a-simple-way-to-overload-functions/30937.
bjorn3 at 2022-08-06 13:46:05
I recently did some experiments with function traits that requires the use of trait specialization to get it working.
https://github.com/nyxtom/composition/blob/main/src/lib.rs
pub trait Func<Args, T> { type Output; fn call(&self, args: Args) -> Self::Output; } // Default implementation of a func for T as output impl<A, B, Args, T> Func<Args, ()> for (A, B) where A: Fn<Args, Output = T>, B: Fn<T>, { type Output = B::Output; #[inline] fn call(&self, args: Args) -> Self::Output { let args = self.0.call(args); self.1.call(args) } } // Subset of (A, B) T is (T,) impl<A, B, Args, T> Func<Args, (T,)> for (A, B) where A: Fn<Args, Output = T>, B: Fn<(T,)>, { type Output = B::Output; #[inline] fn call(&self, args: Args) -> Self::Output { let args = self.0.call(args); self.1.call((args,)) } }Specifically it allowed me to compose between two functions that is already a tuple being returned and apply them as arguments, or in the natural case where the return value is a single type.
fn foo() {} fn test() -> i32 { 3 } fn plus(a: i32) -> i32 { a + 1 } fn multiply(a: i32, b: i32) -> i32 { a * b } fn output() -> (i32, i32) { (4, 2) } fn assert_func<Args, T>(_: impl Func<Args, T>) {} #[test] fn test_assert_funcs() { assert_func((foo, foo)); assert_func((foo, test)); assert_func((test, plus)); assert_func((plus, plus)); assert_func((multiply, plus)); assert_func((output, multiply)); }Does this constitute function overloading or just a use of trait specialization? I did later expand on this to support more composition (than 1 argument) by using the recursive tuple structure like so:
// Subset of (A, B) where A is already a tuple that implements Func impl<A, B, Args, T, F> Func<Args, ((), (), F)> for (A, B) where A: Fn<Args, Output = T>, B: Func<T, F>, { type Output = B::Output; #[inline] fn call(&self, args: Args) -> Self::Output { let args = self.0.call(args); self.1.call(args) } } // Subset of (A, B) where is A is Func and B takes (T,) impl<A, B, Args, T, F> Func<Args, ((), (T,), F)> for (A, B) where A: Fn<Args, Output = T>, B: Func<(T,), F>, { type Output = B::Output; #[inline] fn call(&self, args: Args) -> Self::Output { let args = self.0.call(args); self.1.call((args,)) } }This allowed me to perform expressions like so:
#[test] fn test_assert_nested_func() { assert_func((multiply, (plus, plus))); assert_func((plus, (plus, plus))); assert_func((output, (multiply, plus))); }This does require the use of the
Fn<T>rather than the parenthetical notation but it's quite useful here as it allows some nice composition to happen. As well, you can make use of variadics with just a macro that turns it into a recursive structure.pub struct Function<F, T>(F, PhantomData<T>); impl<F, Args, T> Fn<Args> for Function<F, T> where F: Func<Args, T>, { extern "rust-call" fn call(&self, args: Args) -> Self::Output { self.0.call(args) } } impl<F, Args, T> FnMut<Args> for Function<F, T> where F: Func<Args, T>, { extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output { self.0.call(args) } } impl<F, Args, T> FnOnce<Args> for Function<F, T> where F: Func<Args, T>, { type Output = F::Output; extern "rust-call" fn call_once(self, args: Args) -> Self::Output { self.0.call(args) } } macro_rules! compose { ( $last:expr ) => { $last }; ( $head:expr, $($tail:expr), +) => { ($head, compose!($($tail),+)) }; } macro_rules! func { ( $head:expr, $($tail:expr), +) => { Function(($head, compose!($($tail),+)), PhantomData::default()) }; } fn assert_fn<Args>(_: impl Fn<Args>) {} #[test] fn test_assert_fn() { assert_fn(func!(output, multiply, plus)); }With function composition I can guarantee that the composition of
func!(A, B, C)is the type safe equivalent of the input to A and output of C. The main thing here that makes it easier is havingFn<T>rather thanFn(A). As without the generics argument, I end up having to create an entirely different macro that implements these cases forFn(A), Fn(A, B), Fn(A, B, C), ...etc.I should note that the above example doesn't necessarily require the use of
impl<Args, T> Fn<Args> for Function<Args, T>it just makes it easier to do:compose!(plus, plus, plus).call((4,));vs
func!(plus, plus, plus)(4)nyxtom at 2022-08-06 15:16:31
@bjorn3 Thanks very much, but I cannot understand the difference of higher-order function arguments and arbitrary function signatures(function overloading) clearly.
Thinking of this example, what is the main factor to distinguish the two concepts?
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c13f39de7df4c9fbc26b19fd7da4a197
Is this the correct way forcing not to use arbitrary function signatures by language design?
yozu at 2022-08-06 16:05:48
Higher-order function arguments is when the function arguments only differ by lifetimes.
bjorn3 at 2022-08-06 16:09:18
@bjorn3 If so, the following is different signatures?
fn f<T: AsRef<[u8]>>(t: T) { unimplemented!() } f("hello"); f("hello".to_owned()); f(vec![1,2,3]);yozu at 2022-08-06 16:14:52
"hello".to_owned()andvec![1,2,3]have the same lifetime (they are stack-allocated).&'static Tis special because it is valid for every lifetime.Jay Oster at 2022-08-06 16:42:46
if by stack you mean heap, yes
Lokathor at 2022-08-06 16:46:55
They both happen to point to the heap, but the structs themselves are stack-allocated when used in the argument position like that. Or when assigned to a variable with a
letbinding, for instance.Jay Oster at 2022-08-06 16:48:55
Anyway, the fact that people can already make up their own trait based operator overloading, and even paper over the arity issue with a do_call! macro or whatever, means that there's not much reason to make the Fn traits themselves specifically and magically reject the possibility of overloading. We're just giving people a hard time.
I found tour first reference link as to why not function overloading most interesting because the second reply is from an actual T-lang member that said:
the desire to not have monomorphization-time errors in generics.
and if overloading is happening strictly through the trait system it should end up preventing the post-monomorph errors.
Lokathor at 2022-08-06 17:07:29
Anyway, the fact that people can already make up their own trait based operator overloading, and even paper over the arity issue with a do_call! macro or whatever, means that there's not much reason to make the Fn traits themselves specifically and magically reject the possibility of overloading. We're just giving people a hard time.
I found tour first reference link as to why not function overloading most interesting because the second reply is from an actual T-lang member that said:
the desire to not have monomorphization-time errors in generics.
and if overloading is happening strictly through the trait system it should end up preventing the post-monomorph errors.
This has been my experience (as I've seen implemented in other places). One macro to implement arity to fix the
Fn(A),Fn(A, B)..etc and another to be able to call withdo_call!(A, B, C). Having the generics on the actual trait hereFn<Args>makes this less difficult to work with and avoids the extra macro expansion. That being said, it's not strictly required to have animpl Fn<Args> for Fsince you can do this with the trait based approach but the ability to use theextern "rust-call"hack to turn a struct into a function call is a bit different.nyxtom at 2022-08-06 17:45:38
I agree with the fact you shouldn't be able to have multiple Fn* impl on an item.
This is why I wonder why Args are generic and not a associated type
Maix0 at 2023-03-13 19:04:32
Would it be helpful if I made a PR to move from generic to associated, just to see how it would work?
Ray Redondo at 2023-03-27 20:14:33
Would it be helpful if I made a PR to move from generic to associated, just to see how it would work?
I think it would be helpful, but there would need to be some work inside rustc (don't quote me on that) since Fn* traits are really bypassing the whole "how do we represent arguments" by using a custom syntax.
Tho it would allow you to get the argument out of an Fn* trait type (i don't think it is an issue, and it could be gated behind a perma-unstable flag if we really don't want that to happen).
There is more work to be done, but I believe that we should make this change. Currently it is an non-issue since you can't implement these trait, but with unboxed closure it would allow you to have some kind of function overload because you can implement a generic trait multiple times just with different generics.
Maix0 at 2023-03-27 20:21:18
I'm pretty sure that the fact it is generic can already be easily be relied upon, and is relied upon in practice. I can try to cook up am example soon.
edit: No capacity for this, sorry.
Jonas Platte at 2023-03-27 21:04:16
Would it be helpful if I made a PR to move from generic to associated, just to see how it would work?
I don't think this can work; if
Argswere an associated type, function HRTBs wouldn't be possible (e.g.for<'a> Fn(&'a [i32]) -> &'a i32).moulins at 2023-05-21 02:14:44
I just come here for I want some code like this:
let x = CustomizeStruct; let y = x(); // direct call on instanceAnd seems that it has to do with code like this:
#![feature(unboxed_closures)] #![feature(fn_traits)] struct CustomizeStruct; impl Fn<()> for CustomizeStruct { extern "rust-call" fn call(&self, _args: ()) { println!("call CustomizeStruct"); } } impl FnMut<()> for CustomizeStruct { extern "rust-call" fn call_mut(&mut self, _args: ()) { println!("call CustomizeStruct"); } } impl FnOnce<()> for CustomizeStruct { type Output = (); extern "rust-call" fn call_once(self, _args: ()) { println!("call CustomizeStruct"); } }But due to the instability of the 2 features, which led to me here, then I have to worke around by using
Deref:use std::ops::Deref; struct Tensor { value: i32, name: String, } impl Tensor { fn new(value: i32, name: &str) -> Self { Tensor { value, name: name.to_string(), } } } struct CustomizeStruct { closure: Box<dyn Fn(&Tensor) -> i32>, } impl CustomizeStruct { fn new() -> Self { CustomizeStruct { closure: Box::new(|tensor: &Tensor| { println!("call CustomizeStruct"); println!("Tensor name: {}", tensor.name); tensor.value * 2 }), } } } impl Deref for CustomizeStruct { type Target = dyn Fn(&Tensor) -> i32; fn deref(&self) -> &Self::Target { &*self.closure } } fn main() { let x = CustomizeStruct::new(); let tensor = Tensor::new(21, "example tensor"); let y: i32 = x(&tensor); println!("y = {}", y); }The code above is compilable on v1.72.0.
老董 at 2023-08-25 00:54:36
I have reread this issue and the following issues seem to have been raised as in some sense blocking:
- Stabilising
extern "rust-call". - Open questions about the API of
Args, generic variadic despatch, etc. - Should there be
UnsafeFn*? Answer: no. (not a blocker, therefore) impl FnOnce for &Tcall syntax #42736 apparently due to lack of autoref?- Open questions about the relationship of the blanket impls for these Fn traits (currently there aren't any, but they existed at some point AFAICT).
1, 2 and 5 could be dealt with by desugaring along these lines:
/// plan to stabilise this: impl FnMut(i32) for F { fn call_mut(&mut self, x: i32) { dbg!(x); } } // desugars to: impl FnMut<(i32,)> for F { extern "rust-call" fn call_mut(&mut self, (x,): (i32,)) -> Self::Output { dbg!(x); } } impl FnOnce<(i32,)> for F { type Output = (); extern "rust-call" fn call_once(mut self, args: (i32,)) -> Self::Output { FnMut::call_mut(&mut self, args) } } // Correspondingly: // impl Fn(..) => impl Fn<>, impl FnMut<>, impl FnOnce<> // impl FnOnce(..) => impl FnOnce<> (only)This has the following properties:
- Preserves opacity, and expansion/change possibilities, for the
Fntraits - Does not expose
"rust-call"or the type ofArgs - Makes
impl Fn*magic - which is OK because these are already magic - Prevents a manual implementor providing both (say)
FnMutandFn, but I think preserving ability to make this possible via specialisation later - Unblocks most of the obvious use cases (including wrappers for functions)
Realistically,it seems to me that there is little else that we could want that
impl FnMutto mean in the future.This disposes of all the blockers except 4, #42736, which is a despatch anti-affordance when you
impl FnOnce for &T(or similar). I hope #42736 isn't actually a blocker ?What am I missing?
Ian Jackson at 2023-12-17 16:02:03
- Stabilising
Open questions about the relationship of the blanket impls for these Fn traits (currently there aren't any, but they existed at some point AFAICT).
Isn't that referring to the impls on references and
Box? (both#[fundamental])Josh Stone at 2023-12-17 16:32:22
Isn't that referring to the impls on references and
Box? (both#[fundamental])I was referring to comments like https://github.com/rust-lang/rust/issues/29625#issuecomment-242929622 (references #19032). That's about the relationship between
Fn,FnMutandFnOnce. AFAICT before #23282 there were some blanket impls.I don't think there is any issue with references or Box, that applies to the Fn* traits, but only when the trait(s) are manually implemented?
Ian Jackson at 2023-12-17 16:57:59
I had a go at implementing this:
/// plan to stabilise this: impl FnMut(i32) for F { fn call_mut(&mut self, x: i32) { dbg!(x); } } // desugars to: impl FnMut<(i32,)> for F { extern "rust-call" fn call_mut(&mut self, (x,): (i32,)) -> Self::Output { dbg!(x); } } impl FnOnce<(i32,)> for F { type Output = (); extern "rust-call" fn call_once(mut self, args: (i32,)) -> Self::Output { FnMut::call_mut(&mut self, args) } }But I encountered a difficulty. (I'm not very familiar with the compiler innards, so possibly I'm just going about it entirely the wrong way.)
I was proposing this as a desugaring, and so I think probably this wants to be done during AST lowering. I think the right place to do this would be in
compiler/rustc_ast_lowering/src/item.rs,lower_item_kind. That's the last place where the information needed to do this transformation all exists together.But
lower_item_kindonly gets to produce one outputhir::ItemKindand my proposal calls for multiple impls. I wasn't sure how (or whether) to try to givelower_item_kindthe ability to produce multiple output items.Turning one item into many raises a question about what ought to be done about attributes applied to the user-supplied
implblock. I think they may need to be copied, since pieces of the input end up in more than one of the outputs, and we might want the user's attributes to affecttype Output=as well asfn call_*. Maybe this is a reason this shouldn't be done?I had a go at inventing a helper trait instead: ie, the lowering would implement not the normal
FnMut(say) buthelper::FnMutand a blanket impl would provideops::FnMut. But my blanket impls conflicted with other blanket impls for&impl Fnfor example.Maybe someone else can get this to work or give me some pointers.
Ian Jackson at 2023-12-18 12:56:50
I skimmed this thread so I'm sorry if I missed something, but why is there a difference between the expression of
OutputandArgs? I get thatArgshas to be a parameter to encode generics, but functions can have generic parameters that only appear in their return type. In fact,std::sync::mpsc::channeldoes exactly that:fn channel<T>() -> (Sender<T>, Receiver<T>);This type of generic is fairly common; a trick I use to describe infallible results in a way that is compatible with whatever the user wants to do is to template on the unconstrained error:
fn this_never_fails<E>() -> Result<(), E>Why isn't
Outputalso a generic parameter?Edit: it somehow elided me that
Outputwas already standardized. I guess these traits are just not equivalent to function definitions then.Lawrence Bethlenfalvy at 2023-12-19 13:21:44
@lbfalvy You can think of
fnitems as syntactic sugar for of a struct and some impls. For examplefn example(arg1: u32, arg2: bool) -> String {…}becomes:struct example; impl FnOnce<(u32, bool)> for example { type Output = String; extern "rust-call" fn call_once(self, args: (u32, bool)) -> String {…} }Now if the function itself is generic like
fn channel<T>() -> (Sender<T>, Receiver<T>) {…}, the equivalent expansion would have thatTbe a parameter of the struct. Then the impl can reference it without any problem when defining the associated type:struct channel<T>(PhantomData<T>); impl<T> FnOnce<()> for channel<T> { type Output = (Sender<T>, Receiver<T>); extern "rust-call" fn call_once(self, args: ()) -> (Sender<T>, Receiver<T>) {…} }Simon Sapin at 2023-12-19 16:54:15
@SimonSapin I see, but why can't the same technique be used to make Args an associated type too?
Lawrence Bethlenfalvy at 2023-12-19 23:46:17
@SimonSapin I see, but why can't the same technique be used to make Args an associated type too?
I suppose because there's no varadic associated type support?
You would have to use tuple and it'd make things complicated.
Jiahao XU at 2023-12-20 02:42:07
I believe it is required to handle higher-ranked types. Don't recall exactly why though.
bjorn3 at 2023-12-20 08:58:28
I believe this was also waiting on variadic generics, which are waiting on the type system overhaul.
Ray Redondo at 2024-01-09 21:32:16