Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resource attribute macro #627

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@scrogson FYI

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
Loading