-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Tracking issue for Fn traits (unboxed_closures
& fn_traits
feature)
#29625
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? |
|
if by stack you mean heap, yes |
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: