Skip to content

Commit

Permalink
Add resource_impl attribute macro (#627)
Browse files Browse the repository at this point in the history
Automatically sets the `IMPLEMENTS_*` associated constants and by
default registers the resource type.
  • Loading branch information
filmor authored Jul 9, 2024
1 parent a9b13c5 commit e9a4ab4
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ versions.
receives an `Env` parameter (#617)
- Process monitoring via resources can now be used on resource types that
implement the `Resource::down` callback (#617)
- Resource implementation and registration helper attribute (#627)

### Fixed

Expand Down
9 changes: 6 additions & 3 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ This document is intended to simplify upgrading to newer versions by extending t
2. The functionality related to the `derive` feature is now unconditionally
active. The feature flag is kept for compatibility for now but will be
removed in the future.
3. To register a type as a resource, the new `#[derive(Resource)]` can be used
now. It is implicitly registered and does not require (or work in) the old
explicit registration with `rustler::resource!` a custom `load` function.
3. To use a type as a resource, the `Resource` trait should now be implemented
on the type, which also allows for specifying a destructor (taking an `Env`
argument) or a `down` callback for process monitoring. If the recommended
`resource_impl` attribute is used on the `impl` block, the type will by
default be automatically registered and the `IMPLEMENTS_...` constants will
be set for implemented callbacks.

## 0.32 -> 0.33

Expand Down
3 changes: 3 additions & 0 deletions rustler/src/codegen_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use crate::{Encoder, Env, OwnedBinary, Term};
// Re-export of inventory
pub use inventory;

// Re-export of resource registration
pub use crate::resource::Registration as ResourceRegistration;

// Names used by the `rustler::init!` macro or other generated code.
pub use crate::wrapper::exception::raise_exception;
pub use crate::wrapper::{
Expand Down
4 changes: 2 additions & 2 deletions rustler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ pub use nif::Nif;
pub type NifResult<T> = Result<T, Error>;

pub use rustler_codegen::{
init, nif, NifException, NifMap, NifRecord, NifStruct, NifTaggedEnum, NifTuple, NifUnitEnum,
NifUntaggedEnum,
init, nif, resource_impl, NifException, NifMap, NifRecord, NifStruct, NifTaggedEnum, NifTuple,
NifUnitEnum, NifUntaggedEnum,
};

#[cfg(feature = "serde")]
Expand Down
11 changes: 11 additions & 0 deletions rustler/src/resource/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,20 @@ mod util;
pub use arc::ResourceArc;
pub use error::ResourceInitError;
pub use monitor::Monitor;
pub use registration::Registration;
pub use traits::Resource;
use traits::ResourceExt;

/// Deprecated resource registration method
///
/// This macro will create a local `impl Resource` for the passed type and is thus incompatible
/// with upcoming Rust language changes. Please implement the `Resource` trait directly and
/// register it either using the `resource_impl` attribute or using the `Env::register` method:
/// ```ignore
/// fn on_load(env: Env) -> bool {
/// env.register::<ResourceType>().is_ok()
/// }
/// ```
#[macro_export]
macro_rules! resource {
($struct_name:ty, $env: ident) => {{
Expand Down
15 changes: 14 additions & 1 deletion rustler/src/resource/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ use std::mem::MaybeUninit;
use std::ptr;

#[derive(Debug)]
struct Registration {
pub struct Registration {
get_type_id: fn() -> TypeId,
get_type_name: fn() -> &'static str,
init: ErlNifResourceTypeInit,
}

unsafe impl Sync for Registration {}

inventory::collect!(Registration);

impl<'a> Env<'a> {
/// Register a resource type, see `Registration::register`.
pub fn register<T: Resource>(&self) -> Result<(), ResourceInitError> {
Expand All @@ -34,6 +38,15 @@ impl<'a> Env<'a> {
/// `std::mem::needs_drop`). All other callbacks are only registered if `IMPLEMENTS_...` is set to
/// `true`.
impl Registration {
/// Register all resource types that have been submitted to the inventory.
pub fn register_all_collected(env: Env) -> Result<(), ResourceInitError> {
for reg in inventory::iter::<Registration>() {
reg.register(env)?;
}

Ok(())
}

/// Generate a new (pending) resource type registration.
pub const fn new<T: Resource>() -> Self {
Self {
Expand Down
5 changes: 5 additions & 0 deletions rustler_codegen/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ impl From<InitMacroInput> for proc_macro2::TokenStream {
let mut env = rustler::Env::new_init_env(&env, env);
// TODO: If an unwrap ever happens, we will unwind right into C! Fix this!
let load_info = rustler::Term::new(env, load_info);

if !rustler::codegen_runtime::ResourceRegistration::register_all_collected(env).is_ok() {
return 1;
}

#load.map_or(0, |inner| {
rustler::codegen_runtime::handle_nif_init_call(
inner, env, load_info
Expand Down
61 changes: 61 additions & 0 deletions rustler_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod init;
mod map;
mod nif;
mod record;
mod resource_impl;
mod tagged_enum;
mod tuple;
mod unit_enum;
Expand Down Expand Up @@ -400,3 +401,63 @@ pub fn nif_untagged_enum(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
untagged_enum::transcoder_decorator(&ast).into()
}

/// Helper attribute for `Resource` implementations
///
/// When an `impl Resource for Type` block is annotated with this attribute, it will automatically
/// set the `IMPLEMENTS_...` associated constants for all implemented callback methods. Thus,
/// instead of
///
/// ```ignore
/// struct ResourceType {}
///
/// impl Resource for ResourceType
/// {
/// const IMPLEMENTS_DESTRUCTOR: bool = true;
///
/// fn destructor(...) { ... }
/// }
/// ```
/// it is enough to provide the implementation:
/// ```ignore
/// #[rustler::resource_impl]
/// impl Resource for ResourceType
/// {
/// fn destructor(...) { ... }
/// }
/// ```
///
/// The resource type is also automatically registered by default, it does not have to be manually
/// registered in a `load` callback. The automatic registration can be disabled with the `register`
/// parameter:
///
/// ```ignore
/// #[rustler::resource_impl]
/// impl Resource for ResourceType
/// {
/// ...
/// }
///
/// // no load callback necessary
/// ```
///
/// If registration is disabled, the resource type has to be registered manually. It is not
/// possible to use the old `resource!` macro for this, as that injects another `impl Resource`
/// block.
/// ```ignore
/// #[rustler::resource_impl(register = false)]
/// impl Resource for ResourceType
/// {
/// ...
/// }
///
/// pub fn on_load(env: Env) -> bool {
/// env.register::<ResourceType>().is_ok()
/// }
/// ```
#[proc_macro_attribute]
pub fn resource_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemImpl);

resource_impl::transcoder_decorator(input).into()
}
42 changes: 42 additions & 0 deletions rustler_codegen/src/resource_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::collections::HashSet;

pub fn transcoder_decorator(mut input: syn::ItemImpl) -> TokenStream {
// Should be `Resource` but will fail somewhere else anyway if it isn't.
// let (_, _trait_path, _) = input.trait_.unwrap();
let type_path = match *input.self_ty {
syn::Type::Path(ref type_path) => type_path.clone(),
_ => panic!("Can only implement trait on concrete types"),
};

let mut to_add: HashSet<String> = HashSet::new();
let mut already_has: HashSet<String> = HashSet::new();

for item in input.items.iter() {
if let syn::ImplItem::Fn(f) = item {
to_add.insert(
format!("IMPLEMENTS_{}", f.sig.ident.to_string().to_uppercase()).to_string(),
);
}

if let syn::ImplItem::Const(f) = item {
already_has.insert(f.ident.to_string());
}
}

for add in to_add.difference(&already_has) {
let ident = syn::Ident::new(add, Span::call_site());
let impl_item: syn::ImplItem = syn::parse_quote!(const #ident: bool = true;);

input.items.push(impl_item);
}

quote!(
#input

rustler::codegen_runtime::inventory::submit!(
rustler::codegen_runtime::ResourceRegistration::new::<#type_path>()
);
)
}
9 changes: 5 additions & 4 deletions rustler_tests/native/rustler_test/src/test_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ pub struct TestMonitorResource {
inner: Mutex<TestMonitorResourceInner>,
}

#[rustler::resource_impl(register = true)]
impl Resource for TestMonitorResource {
const IMPLEMENTS_DOWN: bool = true;

fn down<'a>(&'a self, _env: Env<'a>, _pid: LocalPid, mon: Monitor) {
let mut inner = self.inner.lock().unwrap();
assert!(Some(mon) == inner.mon);
Expand All @@ -32,18 +31,20 @@ pub struct ImmutableResource {
b: u32,
}

#[rustler::resource_impl(register = false)]
impl Resource for ImmutableResource {}

pub struct WithBinaries {
a: [u8; 10],
b: Vec<u8>,
}

impl Resource for WithBinaries {}

pub fn on_load(env: Env) -> bool {
rustler::resource!(TestResource, env)
&& env.register::<WithBinaries>().is_ok()
&& env.register::<ImmutableResource>().is_ok()
&& env.register::<TestMonitorResource>().is_ok()
&& rustler::resource!(WithBinaries, env)
}

#[rustler::nif]
Expand Down

0 comments on commit e9a4ab4

Please sign in to comment.