macro_rules! msg_send {
    [super($obj:expr, $superclass:expr), $selector:ident $(,)?] => { ... };
    [super($obj:expr, $superclass:expr), $($selector:ident : $argument:expr $(,)?)+] => { ... };
    [$obj:expr, $selector:ident $(,)?] => { ... };
    [$obj:expr, $($selector:ident : $argument:expr $(,)?)+] => { ... };
}
Expand description

Sends a message to an object or class.

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

General information

The syntax is similar to the message syntax in Objective-C, except we allow an optional comma between arguments (works better with rustfmt).

The first argument (know as the “receiver”) can be any type that implements MessageReceiver, like a reference or a pointer to an object, or even a reference to an rc::Id containing an object. Each subsequent argument must implement Encode.

Behind the scenes this 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, making the ergonomics when using this slightly worse).

Variadic arguments are not currently supported.

Panics

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

And panics if the verify_message feature is enabled and the Objective-C method’s argument’s encoding does not match the encoding of the given arguments. This is highly recommended to enable while testing!

Safety

This macro can’t inspect header files to see the expected types, so it is your responsibility that the selector exists on the receiver, and that the argument types and return type are what the receiver excepts for this selector - similar to defining an external function in FFI.

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

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

This way we are clearly communicating to Rust that this method takes an immutable object, a C-integer, and returns a pointer to (probably) a C-compatible string. Afterwards, it becomes fairly trivial to make a safe abstraction around this.

In particular, you must uphold the following requirements:

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

  2. The types of the receiver and arguments must match what is expected on the Objective-C side.

  3. The call must not violate Rust’s mutability rules, e.g. if passing an &T, the Objective-C method must not mutate the variable (this is true for receivers as well).

  4. If the receiver is a raw pointer the user must ensure that it is 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.

  5. The method must not (yet, see RFC-2945) throw an exception.

  6. 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. Another example, some methods may only be called on the main thread).

  7. TODO: Maybe more?

Examples

let obj: *mut Object;
let description: *const Object = unsafe { msg_send![obj, description] };
let _: () = unsafe { msg_send![obj, setArg1: 1 arg2: 2] };
// Or with an optional comma between arguments:
let _: () = unsafe { msg_send![obj, setArg1: 1, arg2: 2] };