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

RFC: add pyo3-like attribute macros to define nodejs classes and methods #43

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
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
75 changes: 75 additions & 0 deletions text/0000-adding-pyo3-like-attribute-macros.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
- Feature Name: adding_pyo3_like_attribute_macros_for_classes_and_methods
- Start Date: 2021-06-02
- RFC PR: (leave this empty)
- Neon Issue: (leave this empty)

# Summary
[summary]: #summary

This RFC proposes adding [PyO3-like](https://pyo3.rs/v0.13.2/class.html) attribute-macros to define JavaScript classes as Rust structs.

# Motivation
[motivation]: #motivation

At the moment of migration to `napi`, there is no idiomatic way of writing JavaScript classes in pure Rust. This proposal adds the attribute macros to help users to write classes and methods easily.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

I will describe some potential use cases here:
```rust
#[neon::class]
struct Person {
name: string,
age: u8,
}
#[neon::methods]
Copy link
Member

Choose a reason for hiding this comment

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

I think these should be namespaced to help prevent conflicts with other macros. This could be named neon::class_methods or we could have everything in a neon::class module.

#[neon::class::data]
#[neon::class::methods]

Copy link
Author

Choose a reason for hiding this comment

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

Currently neon only has one proc/attribute macro, right? main

Copy link
Member

Choose a reason for hiding this comment

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

That's correct.

Copy link
Author

Choose a reason for hiding this comment

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

I actually prefer the simplicity of #[neon::methods], #[neon::classes] if in a foreseeable future there won't be any namespace conflicts.

impl Person {
fn greet(&self, cx: FunctionContext) -> JsResult<JsString> {
Ok(cx.string(format!("Hello, my name is {}", self.name)))
}

#[neon::constructor]
Copy link
Member

Choose a reason for hiding this comment

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

The current declare_types macro has a two-part initialization with the second part being optional.

  1. (Required). Constructor. This creates the Rust struct that gets wrapped into the JS class.
  2. (Optional). Initialization. This has access to the created class and can perform actions like assigning properties. This can be thought of as using this in a JS constructor.

I think this is valuable and it would be nice to maintain that functionality. I can think of a couple ways to implement it:

  1. Another macro attribute like #[neon::class_init] for that phase
  2. Need to call a function and return a class instead of the data type.
create_class(Person { name, age })

fn new(mut cx: FunctionContext) -> Self {
let name = cx.argument::<JsString>(0)?.value(&mut cx);
let age = cx.argument::<JsNumber>(1)?.value(&mut cx);
Person { name, age }
}
}
```

Then we can use `Person` class as the following
```JavaScript
let p = Person("Peter", 25);
p.greet(); // Hello, my name is Peter
```

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

This is the technical portion of the RFC. Explain the design in sufficient detail that:

- Its interaction with other features is clear.
- It is reasonably clear how the feature would be implemented.
- Corner cases are dissected by example.

This section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work.

# Drawbacks
[drawbacks]: #drawbacks

Why should we _not_ do this?

# Rationale and alternatives
[alternatives]: #alternatives

- Why is this design the best in the space of possible designs?
- What other designs have been considered and what is the rationale for not choosing them?
- What is the impact of not doing this?

# Unresolved questions
Copy link
Member

Choose a reason for hiding this comment

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

Some unresolved questions that I have.

  1. What is the name of the type? Do we generate complete new types or do we have something like JsClass<Person>?
  2. Do we implicitly create a RefCell or do we only allow &self? I think we should start simple and class methods can only take &self. If they take a different form of &self we fail to compile and if they don't take self at all we create a static method.
  3. Are there any other critical features? In my opinion we should start simple and not try to add everything.
  4. What happens if the class is called as a function instead of a constructor?

[unresolved]: #unresolved-questions

- What parts of the design do you expect to resolve through the RFC process before this gets merged?
- What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?