Skip to content

feat: add inheritance support for Rust classes#587

Open
ajihyf wants to merge 3 commits intoDelSkayn:masterfrom
ajihyf:aji/inherit
Open

feat: add inheritance support for Rust classes#587
ajihyf wants to merge 3 commits intoDelSkayn:masterfrom
ajihyf:aji/inherit

Conversation

@ajihyf
Copy link

@ajihyf ajihyf commented Jan 2, 2026

Closes #116 (partly), extending JS classes is excluded from this MR as it requires significant refactoring of class methods.

Description of changes

  • Modify class.rs to expose parent_vtable method for inheritance.
  • Introduce inherits.rs to define HasParent trait.
  • Enhance ffi.rs to include parent vtable in the VTable struct.
  • Update opaque.rs to handle constructors for inherited classes.
  • Adjust macro definitions to support the extends attribute.

Checklist

  • Added change to the changelog
  • Created unit tests for my feature if needed

@ajihyf ajihyf force-pushed the aji/inherit branch 2 times, most recently from ae55ef9 to 2705011 Compare January 2, 2026 04:24
@Sytten
Copy link
Collaborator

Sytten commented Jan 2, 2026

Not a bad addition though the primary usecase is to extend rust from a js class to avoid the shenanigans we currently have to do to create classes like Buffer. Any solution we merge will need to support that.

Also from a larger perspective I dont like the vtable design we currently have with a unique class ID for all rust classes. I would much rather we find a way to use the engine directly, we switched to this system as a patch to avoid clashes of class ID but nobody is really happy with the solution.

@ajihyf
Copy link
Author

ajihyf commented Jan 2, 2026

Thanks for the feedback.

To give some context on my motivation: I am currently working on a DOM-like library, so implementing Rust-to-Rust inheritance (e.g., Node -> Element -> HTMLElement) was my immediate priority.

As for the "extending JS classes" requirement, I have a proposal: we could store the Rust struct (as a hidden object) inside the native JS object using a Symbol. This would incur a small extra lookup cost but would solve the binding issue without requiring a massive refactor right now.

@richarddd
Copy link
Collaborator

Thanks for the feedback.

To give some context on my motivation: I am currently working on a DOM-like library, so implementing Rust-to-Rust inheritance (e.g., Node -> Element -> HTMLElement) was my immediate priority.

As for the "extending JS classes" requirement, I have a proposal: we could store the Rust struct (as a hidden object) inside the native JS object using a Symbol. This would incur a small extra lookup cost but would solve the binding issue without requiring a massive refactor right now.

I don't think it has to be this complicated. We just have to call this function:
https://github.com/quickjs-ng/quickjs/blob/master/quickjs.h#L921C1-L923C69

With newTarget being the class we want to extend.

I added some comments here:
#116 (comment)

@ajihyf
Copy link
Author

ajihyf commented Jan 2, 2026

Thanks for the feedback.
To give some context on my motivation: I am currently working on a DOM-like library, so implementing Rust-to-Rust inheritance (e.g., Node -> Element -> HTMLElement) was my immediate priority.
As for the "extending JS classes" requirement, I have a proposal: we could store the Rust struct (as a hidden object) inside the native JS object using a Symbol. This would incur a small extra lookup cost but would solve the binding issue without requiring a massive refactor right now.

I don't think it has to be this complicated. We just have to call this function: https://github.com/quickjs-ng/quickjs/blob/master/quickjs.h#L921C1-L923C69

With newTarget being the class we want to extend.

I added some comments here: #116 (comment)

As I understand it, passing new.target handles the prototype chain, but we still face an issue regarding the opaque field.

For QuickJS internal/built-in classes: The opaque field is already occupied by the engine (e.g., Uint8Array uses it for array data). This means a Rust-implemented Buffer cannot store its own state there, nor can we access it via get opaque. Without a hidden object, I haven't found a unified way to support extending these classes with class macro and JsClass trait.

@richarddd
Copy link
Collaborator

Sorry for the huge delay and thanks again for this PR @ajihyf ! Code looks fine, just a final think i want to test is that how does polymorphism work across rust and JS? For instance extending a class in Rust then extending from JS? Similarly If a class is extended in JS and we want to extend it from Rust.

Closes DelSkayn#116 (partly), since extending JS classes is excluded from this MR as it requires significant refactoring of class methods.
@ajihyf
Copy link
Author

ajihyf commented Feb 13, 2026

No worries about the delay! @richarddd I've just added a test case for JS extending a Rust class, which works straightforwardly. However, extending a JS class from Rust is still tricky, as it requires storing the Rust pointer (state) inside the JS object. Also, I no longer have an immediate need for this inheritance feature in my project. Please feel free to decide on the status of this PR based on your roadmap. :)

@richarddd
Copy link
Collaborator

No worries about the delay! @richarddd I've just added a test case for JS extending a Rust class, which works straightforwardly. However, extending a JS class from Rust is still tricky, as it requires storing the Rust pointer (state) inside the JS object. Also, I no longer have an immediate need for this inheritance feature in my project. Please feel free to decide on the status of this PR based on your roadmap. :)

I’m very interested in this feature and would like to see it supported. My only concern is whether the current approach might turn into a footgun over time. It may be worth considering changes in quickjs-ng that allow for a cleaner design, preferably one that avoids storing Rust state on JavaScript objects.

From your perspective @ajihyf, what changes or additions to quickjs-ng would help make this implementation cleaner?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend class from rust?

3 participants