The cef_callback_impl
macro isn't all that complex - most of the heavy lifting here is getting done by the trait system. However, it is the jumping-off point into the trait system, so it's a good place to start looking at that.
For reference, here's the macro definition itself:
macro_rules! cef_callback_impl {
(impl $RefCounted:ty: $CType:ty {
$(fn $fn_name:ident(&mut $self:ident, $($field_name:ident: $field_ty:ty: $c_ty:ty),+ $(,)?) $(-> $ret:ty)? $body:block)+
}) => {
impl $RefCounted {
$(
extern "C" fn $fn_name(self_: *mut $CType, $($field_name: $c_ty),+) $(-> $ret)? {
trait Impl {
fn inner(&mut $self, $($field_name: $field_ty),+) $(-> $ret)?;
}
impl Impl for $RefCounted {
#[inline(always)]
fn inner(&mut $self, $($field_name: $field_ty),+) $(-> $ret)? $body
}
let mut this = unsafe { RefCounted::<$CType>::make_temp(self_) };
$(
let mut $field_name: $field_ty = unsafe{ <$field_ty as crate::extern_callback_helpers::CToRustType>::from_c_type($field_name) };
)+
this.inner($($field_name),+)
}
)+
}
};
}
Let's take a look at an example from ResourceRequestHandlerWrapper
. This invocation:
cef_callback_impl! {
impl ResourceRequestHandlerWrapper: cef_resource_request_handler_t {
fn protocol_execution(
&mut self,
browser: Option<Browser>: *mut cef_browser_t,
frame: Option<Frame>: *mut cef_frame_t,
request: Request: *mut cef_request_t,
allow_os_execution: Option<&mut c_int>: *mut c_int,
) {
if self.delegate.on_protocol_execution(
browser.as_ref(),
frame.as_ref(),
&request,
) {
if let Some(allow_os_execution) = allow_os_execution {
*allow_os_execution = 1;
}
}
}
}
}
Expands into this (I've cleaned up the type signatures somewhat, but it's still fundamentally the same code):
impl ResourceRequestHandlerWrapper {
extern "C" fn protocol_execution(
self_: *mut cef_resource_request_handler_t,
browser: *mut cef_browser_t,
frame: *mut cef_frame_t,
request: *mut cef_request_t,
allow_os_execution: *mut c_int,
) {
trait Impl {
fn inner(
&mut self,
browser: Option<Browser>,
frame: Option<Frame>,
request: Request,
allow_os_execution: Option<&mut c_int>,
);
}
impl Impl for ResourceRequestHandlerWrapper {
#[inline(always)]
fn inner(
&mut self,
browser: Option<Browser>,
frame: Option<Frame>,
request: Request,
allow_os_execution: Option<&mut c_int>,
) {
if self.delegate.on_protocol_execution(browser.as_ref(), frame.as_ref(), &request) {
if let Some(allow_os_execution) = allow_os_execution {
*allow_os_execution = 1;
}
}
}
}
let mut this = unsafe { RefCounted::<cef_resource_request_handler_t>::make_temp(self_) };
let browser: Option<&Browser> = unsafe {
<Option<&Browser> as CToRustType>::from_c_type(browser)
};
let frame: Option<&Frame> = unsafe {
<Option<&Frame> as CToRustType>::from_c_type(frame)
};
let request: &Request =
unsafe { <&Request as CToRustType>::from_c_type(request) };
let allow_os_execution: Option<&mut c_int> =
unsafe { <Option<&mut c_int> as CToRustType>::from_c_type(allow_os_execution) };
this.inner(browser, frame, request, allow_os_execution)
}
}
...which doesn't look the best. I wouldn't write that if I were writing the function out by hand, but due to limitations of the macro system I'm unaware of any way to implement this particular macro that would generate the same code structure I'd write manually. Still, each section has a purpose:
trait Impl {
fn inner(
&mut self,
browser: Option<&Browser>,
frame: Option<&Frame>,
request: &Request,
allow_os_execution: Option<&mut c_int>,
);
}
impl Impl for ResourceRequestHandlerWrapper {
#[inline(always)]
fn inner(
&mut self,
browser: Option<&Browser>,
frame: Option<&Frame>,
request: &Request,
allow_os_execution: Option<&mut c_int>,
) {
if self.delegate.on_protocol_execution(browser, frame, request) {
if let Some(allow_os_execution) = allow_os_execution {
*allow_os_execution = 1;
}
}
}
}
It's nice and Rust-y for the method body the user writes to be able to use self
, and this section accomplishes that. We need to declare an associated function on ResourceRequestHandlerWrapper
inside the function body if we'd like to do that. We use a trait instead of a vanilla associated function for scoping reasons: associated functions are always visible on the module level, so if we just did impl ResourceRequestHandlerWrapper fn inner...
, it would conflict with other inner
functions in the other C callbacks. Traits, on the other hand, are only visible inside the function body, so each function can have its own separate Impl
trait without running into naming conflicts.
As a side note, notice how the macro definition takes &mut self
as &mut $self:ident
. This is because self
has some weird interactions with the macro hygiene system. The hygiene system recognizes it as an identifier, so makes self
as declared in the macro body and self
as declared in the macro invocation different values. However, the user can only put self
into the $self
matcher, since the language expects that the first parameter of associated methods is named self
.
let mut this = unsafe { RefCounted::<cef_resource_request_handler_t>::make_temp(self_) };
let browser: Option<&Browser> = unsafe {
<Option<&Browser> as CToRustType>::from_c_type(browser)
};
let frame: Option<&Frame> = unsafe {
<Option<&Frame> as CToRustType>::from_c_type(frame)
};
let request: &Request =
unsafe { <&Request as CToRustType>::from_c_type(request) };
let allow_os_execution: Option<&mut c_int> =
unsafe { <Option<&mut c_int> as CToRustType>::from_c_type(allow_os_execution) };
This section has more type annotations than strictly necessary, though the extra specificity certainly doesn't hurt. However, we don't call the functions converting from C types to Rust types directly, since different operations are necessary for different types and the macro system can't distinguish between different types in more than the most primitive, token-level ways. Instead, we shell out to the CToRustType
helper trait:
pub trait CToRustType {
type CType;
unsafe fn from_c_type(c_type: Self::CType) -> Self;
}
This trait performs the conversion between C types (raw pointers, raw enums, etc.) into the corresponding Rust types. For the most part, the implementations are fairly simple; here's the implementation for Browser
types:
impl CToRustType for Browser {
type CType = *mut cef_sys::cef_browser_t;
unsafe fn from_c_type(c_type: Self::CType) -> Self {
<Browser>::from_ptr_unchecked(c_type)
}
}
We automatically implement CToRustType
for any Option<T>
when the CType
is a pointer (the Pointer
trait is defined elsewhere in the module):
impl<T> CToRustType for Option<T>
where
T: CToRustType,
<T as CToRustType>::CType: Pointer,
{
type CType = <T as CToRustType>::CType;
unsafe fn from_c_type(c_type: Self::CType) -> Self {
if c_type.is_null() {
None
} else {
Some(<T as CToRustType>::from_c_type(c_type))
}
}
}
We then pass those arguments onto the inner
function, which process the types and returns the expected value. We don't do any processing on the return type since the trait functions' return types don't consistently map onto CEF types.