I need a sealed element tree


#1

Hello. I know inheritance is bad, but my purpose is different. I’m (still) writting a library which will provide a sealed element tree interface.

HTML elements are also sealed. If you think well of JS, you can’t do this:

class Main extends HTMLDivElement {
    // ....
}

new Main // Uncaught TypeError: Illegal constructor

Extending any DOM thing fails. It’s like a specific type friendship. All classes from Node to HTML elements have a inheritance friendship.

In particular I just want a simple tree where Drawable is the sealed base of some objects, like Grid, Rect and Canvas. It’s easy and elegant to use traits for this. Note traits aren’t meant for sealed trees; someone could easily extend the library drawables.

use Rf<O> = Rc<Cell<O>>;
pub use DrawableRef = Rf<Drawable>;

/// Trait for drawable objects.
///
pub trait Drawable {
    fn draw(&mut self);
    fn bounds(&mut self) -> &mut Bounds;
}

The issue is that calling #bounds() in a random Rc<Cell<Drawable>> would have a certain overhead. That’s all my concern when using traits.

I’d like to be able to somehow have a base Drawable struct, where its sub-types would Deref to it and thus I could access bounds() always directly. I’ve already used unsafe code for this purpose, but it’s not elegant and, after all, I’ve to manually implement and call explicit cast methods (i.e., any_drawable.as_grid() and any_grid.as_drawable()).

If anyone has any better ideas than traits anyway, please tell me. Maybe these #bounds() overheads might not be intensive.


#2

What you’re looking for is virtual dispatch which is more or less the same thing as implementing an interface in C# (I’m unaware of the equivalent in Java as in your example) and will probably have an overhead no matter what language you’re using.
On the other hand, you could use generics or impls where you write your methods like so:

pub fn do_something_with_debug(item: &impl std::fmt::Debug)

(which is equivalent to

pub fn do_something_with_debug<T: std::fmt::Debug>(item: &T)

)
But if you’re originally using a set of dyn Trait objects, then the impl method doesn’t take away the overhead, because when an object is turned into a dyn Trait object, then its original type can’t be recovered, (I.E. the following wouldn’t work:

let num = 3usize;
let virtual_dispatch: Box<dyn Debug> = Box::new(num);
let new_num = *virtual_dispatch as usize;

That wouldnt be logical)
and impl is basically just generic syntactical sugar (Which requires a concrete type which dyn doesn’t provide).

PS. dyn Trait is the new syntax for a trait object type (The type within Rf<Drawable> in your example should become Rf<dyn Drawable>)