-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Tracking issue for Fn traits (unboxed_closures
& fn_traits
feature)
#29625
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
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. |
|
Could someone summarise what is blocking stabilisation here please? |
@nrc uncertainty about |
There should be a way of casting |
@brunoczim That coercion already happens implicitly, but use std::rc::Rc;
fn main() {
let _: &Fn() = &main;
let _: Box<Fn()> = Box::new(main);
let _: Rc<Fn()> = Rc::new(main);
} |
One issue we've been discussing on TheDan64/inkwell#5 is the ability to mark something as For context, |
There is another reason to add
|
@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. |
@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. |
@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. |
With every unsafe function comes a manually written "contract" saying when that function may or may not be called. With |
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 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. |
I don't know how one would realistically implement this, but probably the ideal semantics is to have any function that takes an |
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 |
Is there a way to implement these as wrapper traits? |
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 |
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:
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 |
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 |
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. |
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. |
I don't think this can work; if |
I just come here for I want some code like this: let x = CustomizeStruct;
let y = x(); // direct call on instance And 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 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. |
I have reread this issue and the following issues seem to have been raised as in some sense blocking:
1, 2 and 5 could be dealt with by desugaring along these lines:
This has the following properties:
Realistically,it seems to me that there is little else that we could want that This disposes of all the blockers except 4, #42736, which is a despatch anti-affordance when you What am I missing? |
Isn't that referring to the impls on references and |
I was referring to comments like #29625 (comment) (references #19032). That's about the relationship between 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? |
I had a go at implementing this:
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 But Turning one item into many raises a question about what ought to be done about attributes applied to the user-supplied I had a go at inventing a helper trait instead: ie, the lowering would implement not the normal Maybe someone else can get this to work or give me some pointers. |
I skimmed this thread so I'm sorry if I missed something, but why is there a difference between the expression of 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 Edit: it somehow elided me that |
@lbfalvy You can think of 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 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>) {…}
} |
@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. |
I believe it is required to handle higher-ranked types. Don't recall exactly why though. |
I believe this was also waiting on variadic generics, which are waiting on the type system overhaul. |
Tracks stabilization for the
Fn*
traits.Random bugs:
FnOnce
doesn't seem to support type-based dispatch (asAdd
, etc. do) #45510 – type-based dispatch not workingFn*
traits on references #42736 –foo()
sugar doesn't work where you have&Foo: FnOnce
The text was updated successfully, but these errors were encountered: