extern_protocol

Macro extern_protocol 

Source
macro_rules! extern_protocol {
    (
        // The special #[name = $name:literal] attribute is supported here.
        $(#[$($attrs:tt)*])*
        $v:vis unsafe trait $protocol:ident $(: $conforms_to:ident $(+ $conforms_to_rest:ident)*)? {
            $($methods:tt)*
        }
    ) => { ... };
}
Expand description

Create a new trait to represent a protocol.

This is similar to a @protocol declaration in Objective-C.

See Protocols - The Objective-C Programming Language and Working with Protocols - Programming with Objective-C for general information about protocols in Objective-C.

This macro will create an unsafe trait with methods that provide access to the functionality exposed by the protocol.

Conforming to the protocol can be done in two ways:

Objective-C has a smart feature where you can write id<MyProtocol>, and then work with the protocol as-if it was an object; this is very similar to dyn traits in Rust, and we implement it in a similar way, see ProtocolObject for details.

§Specification

The syntax is similar enough to Rust syntax that if you invoke the macro with parentheses (as opposed to curly brackets), rustfmt will be able to format the contents.

This macro creates an unsafe trait with the specified methods. A default implementation of the method is generated based on the selector specified with #[unsafe(method(a:selector:))]. Similar to extern_methods!, you can use the #[unsafe(method_family = ...)] attribute to override the inferred method family.

Other protocols that this protocol conforms to / inherits can be specified as supertraits.

The new trait T is automatically implemented for ProtocolObject<dyn T>, which also means that ProtocolType is implemented for dyn T.

Finally, you can use the #[optional] attribute to mark optional methods. This currently doesn’t have any effect, but probably will have one in the future when implementing protocols in define_class!.

This macro otherwise shares similarities with extern_class! and extern_methods!.

§Safety

The following are required for using the macro itself:

  • The specified name must be an existing Objective-C protocol.
  • The protocol must actually inherit/conform to the protocols specified as supertraits.

Each method is annotated with #[unsafe(method(...))], where you are responsible for ensuring that the declaration is correct.

While the following are required when implementing the unsafe trait for a new type:

  • The type must represent an object that implements the protocol.

§Examples

Create a trait to represent the NSItemProviderWriting protocol (in practice, you would import this from objc2-foundation, this is just for demonstration purposes).

use std::ffi::c_void;
use objc2::ffi::NSInteger;
use objc2::rc::Retained;
use objc2::runtime::{NSObject, NSObjectProtocol};
use objc2::extern_protocol;
use objc2_foundation::{NSArray, NSString, NSProgress, NSItemProviderRepresentationVisibility};

extern_protocol!(
    /// This comment will appear on the trait as expected.
    //
    // We could have named the trait something else on the Rust-side, and
    // then used this to keep it correct from Objective-C.
    // #[name = "NSItemProviderWriting"]
    //
    // SAFETY:
    // - The name is correct.
    // - The protocol does inherit from `NSObjectProtocol`.
    pub unsafe trait NSItemProviderWriting: NSObjectProtocol {
        //                                  ^^^^^^^^^^^^^^^^
        // This protocol inherits from the `NSObject` protocol

        // This method we mark as `unsafe`, since we aren't using the
        // correct type for the completion handler.
        #[unsafe(method(loadDataWithTypeIdentifier:forItemProviderCompletionHandler:))]
        unsafe fn loadData(
            &self,
            type_identifier: &NSString,
            completion_handler: *mut c_void,
        ) -> Option<Retained<NSProgress>>;

        // SAFETY: The method is correctly specified.
        #[unsafe(method(writableTypeIdentifiersForItemProvider))]
        fn writableTypeIdentifiersForItemProvider_class()
            -> Retained<NSArray<NSString>>;

        // The rest of these are optional, which means that a user of
        // `define_class!` would not need to implement them.

        // SAFETY: The method is correctly specified.
        #[unsafe(method(writableTypeIdentifiersForItemProvider))]
        #[optional]
        fn writableTypeIdentifiersForItemProvider(&self)
            -> Retained<NSArray<NSString>>;

        // SAFETY: The method is correctly specified.
        #[unsafe(method(itemProviderVisibilityForRepresentationWithTypeIdentifier:))]
        #[optional]
        fn itemProviderVisibilityForRepresentation_class(
            type_identifier: &NSString,
        ) -> NSItemProviderRepresentationVisibility;

        // SAFETY: The method is correctly specified.
        #[unsafe(method(itemProviderVisibilityForRepresentationWithTypeIdentifier:))]
        #[optional]
        fn itemProviderVisibilityForRepresentation(
            &self,
            type_identifier: &NSString,
        ) -> NSItemProviderRepresentationVisibility;
    }
);

// Types can now implement `NSItemProviderWriting`, and use the methods
// from it as we specified.

See the source code of objc2-foundation for many more examples.