Macro objc2::msg_send

source ·
macro_rules! msg_send {
    [super($obj:expr), $($selector_and_arguments:tt)+] => { ... };
    [super($obj:expr, $superclass:expr), $($selector_and_arguments:tt)+] => { ... };
    [$obj:expr, $($selector_and_arguments:tt)+] => { ... };
}
Expand description

Send a message to an object or class.

This is wildly unsafe, even more so than sending messages in Objective-C, because this macro can’t inspect header files to see the expected types, and because Rust has more safety invariants to uphold. Make sure to review the safety section below!

The recommended way of using this macro is by defining a wrapper function:

unsafe fn do_something(obj: &NSObject, arg: c_int) -> *const c_char {
    msg_send![obj, doSomething: arg]
}

This way we are clearly communicating to Rust that: The method doSomething: works with a shared reference to the object. It takes a C-style signed integer, and returns a pointer to what is probably a C-compatible string. Now it’s much, much easier to make a safe abstraction around this!

There exists a variant of this macro, msg_send_id!, which can help with upholding certain requirements of methods that return Objective-C’s id, or other object pointers. Use that whenever you want to call such a method!

Specification

The syntax is somewhat similar to the message syntax in Objective-C, except with a comma between arguments. Eliding the comma is possible, but it is soft-deprecated, and will be fully deprecated in a future release. The deprecation can be tried out with the "unstable-msg-send-always-comma" feature flag.

The first expression, know as the “receiver”, can be any type that implements MessageReceiver, like a reference or a pointer to an object. Additionally, it can even be a reference to an rc::Id containing an object.

The expression can be wrapped in super, with an optional superclass as the second argument. If no specific superclass is specified, the direct superclass is retrieved from ClassType.

All arguments, as well as the return type, must implement Encode (bar the exceptions below).

If the last argument is the special marker _, the macro will return a Result<(), Id<E>>, see below.

This macro roughly translates into a call to sel!, and afterwards a fully qualified call to MessageReceiver::send_message. Note that this means that auto-dereferencing of the receiver is not supported, and that the receiver is consumed. You may encounter a little trouble with &mut references, try refactoring into a separate method or reborrowing the reference.

Variadic arguments are currently not supported.

bool handling

Objective-C’s BOOL is slightly different from Rust’s bool, and hence a conversion step must be performed before using it. This is very easy to forget (because it’ll happen to work in most cases), so this macro does the conversion step automatically whenever an argument or the return type is bool.

That means that any Objective-C method that take or return BOOL can be translated to use bool on the Rust side.

If you want to handle the conversion explicitly, or the Objective-C method expects e.g. a pointer to a BOOL, use runtime::Bool instead.

Out-parameters

Parameters like NSString** in Objective-C are passed by “writeback”, which means that the callee autoreleases any value that they may write into the parameter.

This macro has support for passing such parameters using the following types:

  • &mut Id<_>
  • Option<&mut Id<_>>
  • &mut Option<Id<_>>,
  • Option<&mut Option<Id<_>>>

Beware with the first two, since they will cause undefined behaviour if the method overwrites the value with nil.

See clang’s documentation for more details.

Errors

The most common place you’ll see out-parameters is as NSError** the last parameter, which is used to communicate errors to the caller, see Error Handling Programming Guide For Cocoa.

Similar to Swift’s importing of error parameters, this macro supports an even more convenient version than the out-parameter support, which transforms methods whose last parameter is NSError** and returns BOOL, into the Rust equivalent, the Result type.

In particular, if you make the last argument the special marker _, then the macro will return a Result<(), Id<E>> (where you must specify E yourself, usually you’d use icrate::Foundation::NSError).

At runtime, we create the temporary error variable for you on the stack and send it as the out-parameter to the method. If the method then returns NO/false (or in the case of msg_send_id!, NULL), the error variable is loaded and returned in Err.

Do beware that this is only valid on methods that return BOOL, see msg_send_id! for methods that return instance types.

Panics

Panics if the "catch-all" feature is enabled and the Objective-C method throws an exception. Exceptions may still cause UB until extern "C-unwind" is stable, see RFC-2945.

Panics if debug_assertions are enabled and the Objective-C method’s encoding does not match the encoding of the given arguments and return.

And panics if the NSError** handling functionality described above is used, and the error object was unexpectedly NULL.

Safety

Similar to defining and calling an extern function in a foreign function interface. In particular, you must uphold the following requirements:

  1. The selector corresponds to a valid method that is available on the receiver.

  2. The argument types match what the receiver excepts for this selector.

  3. The return type match what the receiver returns for this selector.

  4. The call must not violate Rust’s mutability rules, for example if passing an &T, the Objective-C method must not mutate the variable (except if the variable is inside std::cell::UnsafeCell or derivatives).

  5. If the receiver is a raw pointer it must be valid (aligned, dereferenceable, initialized and so on). Messages to null pointers are allowed (though heavily discouraged), but only if the return type itself is a pointer.

  6. The method must not (yet) throw an exception.

  7. You must uphold any additional safety requirements (explicit and implicit) that the method has. For example:

    • Methods that take pointers usually require that the pointer is valid, and sometimes non-null.
    • Sometimes, a method may only be called on the main thread.
    • The lifetime of returned pointers usually follows certain rules, and may not be valid outside of an autoreleasepool (msg_send_id! can greatly help with that).
  8. Each out-parameter must have the correct nullability, and the method must not have any attributes that changes the how it handles memory management for these.

  9. TODO: Maybe more?

Examples

Sending messages to an object.

use objc2::msg_send;
use objc2::runtime::NSObject;

let obj: *mut NSObject;
let description: *const NSObject = unsafe { msg_send![obj, description] };
// Usually you'd use msg_send_id here ^
let _: () = unsafe { msg_send![obj, setArg1: 1i32, arg2: true] };
let arg1: i32 = unsafe { msg_send![obj, getArg1] };
let arg2: bool = unsafe { msg_send![obj, getArg2] };

Sending messages to the direct superclass of an object.

use objc2::msg_send;

let obj: &MyObject; // Some object that implements ClassType
let _: () = unsafe { msg_send![super(obj), someMethod] };

Sending messages to a specific superclass of an object.

use objc2::msg_send;
use objc2::runtime::{AnyClass, NSObject};

// Since we specify the superclass ourselves, this doesn't need to
// implement ClassType
let obj: *mut NSObject;
let superclass: &AnyClass;
let arg3: u32 = unsafe { msg_send![super(obj, superclass), getArg3] };

Sending a message with automatic error handling.

use objc2::msg_send;
use objc2::rc::Id;

let obj: &NSBundle;
// The `_` tells the macro that the return type should be `Result`.
let res: Result<(), Id<NSError>> = unsafe {
    msg_send![obj, preflightAndReturnError: _]
};

Sending a message with an out parameter and automatic error handling.

use objc2::msg_send;
use objc2::rc::Id;

let obj: &NSFileManager;
let url: &NSURL;
let mut result_url: Option<Id<NSURL>> = None;
unsafe {
    msg_send![
        obj,
        trashItemAtURL: url,
        resultingItemURL: Some(&mut result_url),
        error: _
    ]?
//   ^ is possible on error-returning methods, if the return type is specified
};

// Use `result_url` here