Skip to content
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

Adding a method isn't ergonomic #12

Open
SSheldon opened this issue Feb 16, 2015 · 7 comments
Open

Adding a method isn't ergonomic #12

SSheldon opened this issue Feb 16, 2015 · 7 comments

Comments

@SSheldon
Copy link
Owner

Adding a method to a ClassDecl isn't ergonomic because you need to cast the function to a function pointer, like so:

add_method(sel!(setFoo:), my_obj_set_foo as extern fn(&mut Object, Sel, u32))

This is a bummer because you need to repeat the types that you've already specified.

This wasn't always the case for Rust, but this behavior changed in rust-lang/rust#19891. rust-lang/rust#21086 was opened about this, but was closed as expected behavior.

@SSheldon SSheldon changed the title Creating a MethodDecl isn't ergonomic Adding a method isn't ergonomic May 10, 2015
@aldanor
Copy link

aldanor commented Mar 21, 2017

Would this work?

add_method(sel!(setFoo:), my_obj_set_foo as extern fn(_))

@SSheldon
Copy link
Owner Author

Unfortunately not, the compiler doesn't seem to be able to tell what we mean by that cast. Gives an error like:

error[E0277]: the trait bound `extern "C" fn(_): declare::MethodImplementation` is not satisfied
   --> src/test_utils.rs:104:18
    |
104 |             decl.add_method(sel!(setFoo:), custom_obj_set_foo as extern fn(_));
    |                  ^^^^^^^^^^ the trait `declare::MethodImplementation` is not implemented for `extern "C" fn(_)`

@quadrupleslap
Copy link

This is probably its own issue, but why does this implement Encoding...

modified_fun as extern fn(&mut Object, Sel, &'static mut Object)

... when this doesn't?

fun as extern fn(&mut Object, Sel, &mut Object)

@SSheldon
Copy link
Owner Author

SSheldon commented Feb 4, 2018

@quadrupleslap ... huh, that's very weird. I was able to repro with this error message:

error[E0277]: the trait bound `for<'r, 's> extern "C" fn(&'r mut objc::runtime::Object, objc::runtime::Sel, &'s mut objc::runtime::Object): objc::declare::MethodImplementation` is not satisfied

the trait `objc::declare::MethodImplementation` is not implemented for `for<'r, 's> extern "C" fn(&'r mut objc::runtime::Object, objc::runtime::Sel, &'s mut objc::runtime::Object)`

help: the following implementations were found:
        <for<'r> extern "C" fn(&'r mut T, objc::runtime::Sel, A) -> R as objc::declare::MethodImplementation>
        <for<'r> extern "C" fn(&'r T, objc::runtime::Sel, A) -> R as objc::declare::MethodImplementation>

I thought maybe that the implementation of MethodImplementation was inferring some 'static bounds, but adding generic lifetime bounds to the argument types didn't help. I've tried contorting the declaration various ways and adding all sorts of zany lifetime bounds and for<'a>, but I'm stumped.

@ndarilek
Copy link

I'm actually having a tough time with this. Very new to ObjC, just trying to knock together a Rust binding for a small MacOS API surface I need for my crate so I can avoid ObjC entirely.

I'm trying to implement a callback for this delegate and stay as close to ObjC as I can (I.e. minimal use of cocoa and other crates, which don't implement this API anyway.) I have:

        let mut decl = ClassDecl::new("MyNSSpeechSynthesizerDelegate", class!(NSObject)).unwrap();
        extern "C" fn speech_synthesizer_did_finish_speaking(
            _: &Object,
            _: Sel,
            _: &Object,
            _: BOOL,
        ) {
            println!("Got it");
        }
        unsafe {
            decl.add_method(
                sel!(speechSynthesizer:didFinishSpeaking:),
                speech_synthesizer_did_finish_speaking
                    as extern "C" fn(&Object, Sel, &Object, BOOL) -> (),
            )
        };

That gives me:

error[E0277]: the trait bound `for<'r, 's> extern "C" fn(&'r objc::runtime::Object, objc::runtime::Sel, &'s objc::runtime::Object, i8): objc::declare::MethodImplementation` is not satisfied
  --> src/backends/ns_speech_synthesizer.rs:31:17
   |
31 | /                 speech_synthesizer_did_finish_speaking
32 | |                     as extern "C" fn(&Object, Sel, &Object, BOOL) -> (),
   | |_______________________________________________________________________^ the trait `objc::declare::MethodImplementation` is not implemented for `for<'r, 's> extern "C" fn(&'r objc::runtime::Object, objc::runtime::Sel, &'s objc::runt
ime::Object, i8)`
   |
   = help: the following implementations were found:
             <for<'r> extern "C" fn(&'r T, objc::runtime::Sel, A, B) -> R as objc::declare::MethodImplementation>
             <for<'r> extern "C" fn(&'r mut T, objc::runtime::Sel, A, B) -> R as objc::declare::MethodImplementation>

I've looked at the definition of MethodImplementation but it appears to be created by macros, and mentally parsing macros isn't my strong suit. Removing the third argument (I.e. second &Object arg) makes this compile, but this forum post seemed to indicate that was the wrong method declaration for the delegate I'm trying to implement. The delegate is never called, even though I manually create and run a NSRunLoop in a console-based example, so I'm strongly suspecting that I'm not defining my method correctly for the delector the delegate is checking for.

Anyway, not sure where else to turn for help with this, but having spent 6 hours trying to figure out how to match this delegate selector to a method and failing does suggest that method definition could be a bit more ergonomic. :) Otherwise, thanks for creating this. Didn't take me very long to call into MacOS ObjC APIs without knowing a whole lot about them, and I'm hopeful that this delegate issue is the last I'll face with ObjC interop for this particular task (though maybe that's a bit naive of me. :)

@ndarilek
Copy link

I cracked it with some help. The second &Object needs to be *const Object. With that change, plus my use of NSRunLoop, delegates now seem to work.

madsmtm added a commit to madsmtm/rust-objc that referenced this issue Oct 1, 2021
Support architectures with pointer width 16
@madsmtm
Copy link

madsmtm commented May 28, 2022

So the issue is... Quite complex, and honestly feels more like a bug in the compiler (though I'm not really qualified to state that).

In short, it's because our implementation of MethodImplementation for fn-pointers doesn't (can't with current compiler as far as I know) take into account higher-rank trait bounds, but when coercing your function to a function pointer, you're implicitly telling the compiler that the arguments have higher-ranked lifetimes.

See this playground link, and the Rust tracking issue rust-lang/rust#56105.

To make this concrete, I'll try to answer some of the questions that has been raised here:

add_method(sel!(setFoo:), my_obj_set_foo as extern fn(_))

This won't work because methods have more than one argument, but you're only specifying one with extern fn(_).

But even if you specified the correct number of arguments (extern fn(_, _, _)), it wouldn't work either because our implementation of MethodImplementation requires the first parameter to have a higher-ranked lifetime.

why does this implement Encoding...

modified_fun as extern fn(&mut Object, Sel, &'static mut Object)

... when this doesn't?

fun as extern fn(&mut Object, Sel, &mut Object)

These two snippets desugar to:

modified_fun as for<'a> extern "C" fn(&'a mut Object, Sel, &'static mut Object)
fun as for<'a, b> extern "C" fn(&'a mut Object, Sel, &'b mut Object)

And only the first one matches our implementation of MethodImplementation, &'static mut Object can be substituted for A: Encode, but for<'b> &'b mut Object isn't a real type, and as such can't.

        let mut decl = ClassDecl::new("MyNSSpeechSynthesizerDelegate", class!(NSObject)).unwrap();
        extern "C" fn speech_synthesizer_did_finish_speaking(
            _: &Object,
            _: Sel,
            _: &Object,
            _: BOOL,
        ) {
            println!("Got it");
        }
        unsafe {
            decl.add_method(
                sel!(speechSynthesizer:didFinishSpeaking:),
                speech_synthesizer_did_finish_speaking
                    as extern "C" fn(&Object, Sel, &Object, BOOL) -> (),
            )
        };

You can fix this by doing:

decl.add_method(
    sel!(speechSynthesizer:didFinishSpeaking:),
    speech_synthesizer_did_finish_speaking
        as extern "C" fn(&Object, Sel, _, BOOL),
)

But your solution with *const Object also works.

madsmtm added a commit to madsmtm/objc2 that referenced this issue Jul 7, 2022
Previously, the receiver's lifetime was higher-ranked, while the rest of the arguments weren't, which meant that:
- Their type could not be inferred like the other arguments
- Having a return type with a lifetime bound to the receiver was impossible

Fixes upstream SSheldon/rust-objc#12, at least as far as possible right now.

This is a breaking change, and is unfortunately difficult for users to know how to fix, not really sure how to improve that situation?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants