macro_rules! extern_class {
(
// The following special attributes are supported:
// - #[unsafe(super($($superclasses:path),*))]
// - #[unsafe(super = $superclass:path)]
// - #[thread_kind = $thread_kind:path]
// - #[name = $name:literal]
//
// As well as the following standard attributes:
// - #[derive(Eq, PartialEq, Hash, Debug)] (only those four are supported)
// - #[cfg(...)]
// - #[cfg_attr(..., ...)] (only for standard attributes)
// - #[doc(...)]
// - #[deprecated(...)]
// - #[allow/expect/warn/deny/forbid]
//
// Note that `#[repr(...)]` and `#[non_exhaustive]` are intentionally not supported.
$(#[$($attrs:tt)*])*
$v:vis struct $class:ident;
) => { ... };
(
// Generic version. Currently pretty ill supported.
$(#[$($attrs:tt)*])*
$v:vis struct $class:ident<
$($generic:ident $(: $(?$bound_sized:ident)? $($bound:ident)?)? $(= $default:ty)?),*
$(,)?
>;
) => { ... };
}
Expand description
Create a new type to represent a class.
This is similar to an @interface
declaration in Objective-C.
It is useful for things like objc2-foundation
, which needs to create
interfaces to existing, externally defined classes like NSString
,
NSURL
and so on, but can also be useful for users that have custom
classes written in Objective-C that they want to access from Rust.
§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 (so e.g. as extern_class!( ... );
).
The macro creates an opaque struct containing the superclass (which means that auto traits are inherited from the superclass), and implements the following traits for it to allow easier usage as an Objective-C object:
RefEncode
Message
Deref<Target = $superclass>
ClassType
DowncastTarget
AsRef<$inheritance_chain>
Borrow<$inheritance_chain>
If generics are specified, these will be placed in a PhantomData
.
§Attributes
You can add most normal attributes to the class, including #[cfg(...)]
,
#[allow(...)]
and doc comments.
Exceptions and special attributes are noted below.
§#[unsafe(super(...))]
(required)
Controls the superclass and the rest of the inheritance chain. This attribute is required.
Due to Rust trait limitations, specifying e.g. the superclass NSData
would not give you the ability to convert via AsRef
to NSObject
.
Therefore, you can optionally specify additional parts of the inheritance
in this attribute.
§#[thread_kind = ...]
(optional)
Controls the thread kind, i.e. it can be
set to MainThreadOnly
if the object is only usable on the main thread.
§#[name = "..."]
(optional)
Controls the name of the class.
If not specified, this will default to the struct name.
§#[derive(...)]
This is overridden, and only works with PartialEq
, Eq
, Hash
and Debug
.
§#[cfg_attr(..., ...)]
This is only supported for attributes that apply to the struct itself (i.e. not supported for attributes that apply to implementations, or any of the custom attributes).
§#[repr(...)]
Not allowed (the macro uses this attribute internally).
§Safety
When writing #[unsafe(super(...))]
, you must ensure that:
- The first superclass is correct.
- The thread kind is set to
MainThreadOnly
if the class can only be used from the main thread.
§Examples
Create a new type to represent the NSFormatter
class (for demonstration,
objc2_foundation::NSFormatter
exist for exactly this purpose).
use objc2_foundation::{NSCoding, NSCopying, NSObjectProtocol};
use objc2::rc::Retained;
use objc2::runtime::NSObject;
use objc2::{extern_class, extern_conformance, msg_send, ClassType};
extern_class!(
/// An example description, to show that doc comments work.
// Specify the superclass, in this case `NSObject`
#[unsafe(super(NSObject))]
// We could specify that the class is only usable on the main thread.
// #[thread_kind = MainThreadOnly];
// And specify the name of the class, if it differed from the struct.
// #[name = "NSFormatter"];
// These derives use the superclass' implementation.
#[derive(PartialEq, Eq, Hash, Debug)]
pub struct NSFormatter;
);
// Note: We have to specify the protocols for the superclasses as well,
// since Rust doesn't do inheritance.
extern_conformance!(unsafe impl NSObjectProtocol for NSFormatter {});
extern_conformance!(unsafe impl NSCopying for NSFormatter {});
extern_conformance!(unsafe impl NSCoding for NSFormatter {});
fn main() {
// Provided by the implementation of `ClassType`
let cls = NSFormatter::class();
// `NSFormatter` implements `Message`:
let obj: Retained<NSFormatter> = unsafe { msg_send![cls, new] };
}
Represent the NSDateFormatter
class, using the NSFormatter
type we
declared previously to specify as its superclass.
use objc2_foundation::{NSCoding, NSCopying, NSObjectProtocol};
use objc2::runtime::NSObject;
use objc2::{extern_class, extern_conformance, ClassType};
extern_class!(
// Specify the correct inheritance chain
#[unsafe(super(NSFormatter, NSObject))]
#[derive(PartialEq, Eq, Hash, Debug)]
pub struct NSDateFormatter;
);
// Similarly, we can specify the protocols that this implements here:
extern_conformance!(unsafe impl NSObjectProtocol for NSDateFormatter {});
extern_conformance!(unsafe impl NSCopying for NSDateFormatter {});
extern_conformance!(unsafe impl NSCoding for NSDateFormatter {});
See the source code of objc2-foundation
for many more examples.