Related Rc types; how to Deref 'til the base one?

#1

Say I’ve an Element with methods like x() and offset_width() and floor sub-types like Sprite and Canvas, all of which wrap a internal i::N type (i::Element, i::Sprite and i::Canvas) with Rc. Is it possible to Deref 'til Element from sub-type instances w/o Clone-ing the inner Rc?

E.g.:

sprite.x() // Same as sprite.into::<Element>.x(), but w/o Clone-ing? 

This is for a macro of 1st-floor classes.

0 Likes

#2

I’m a little confused, because your description sounds like inheritance, but Rust doesn’t have one.

If you’ve implemented traits like trait Sprite: Element, that’s not inheritance, that’s a requirement that things implementing Element need to also implement Sprite. If that’s the case, then use Element should be enough to be able to call Element's methods. You may also do <Sprite as Element>::x(sprite) to force that to be explicit.

Another option is if you’ve used composition struct Sprite {element: Element}, and added Deref for element. In that case (*sprite).x() might work.

0 Likes

#3

Yeah, one can set up a std::ops::Deref-based deref chain, and have the compiler follow that chain until it finds x() (or doesn’t, and errors). Example. This is considered an anti-pattern, however - Deref is mostly reserved for smart pointers; these are intentionally as transparent as possible (hence Deref to the inner type they’re managing), “merely” providing some memory management facilities on top. It doesn’t sound like Element, Sprite, Canvas, etc are in similar vein.

@hydroper, it’s also unclear what exactly x() is - the above Deref approach would require that it’s x(&self), but your sprite.into::<Element>.x() note suggests it might be x(self), which means Deref won’t work since you’d need to consume the inner element (unless Element is Copy).

0 Likes

#4

What does “floor” mean? You used it twice in a jargony way (“floor sub-type”, “1st-floor classes”) but it doesn’t mean anything to me.

I googled but all I got were pelvic floor exercises, mediocre programming tutorials demonstrating what is almost certainly the wrong way to model a house, and references to the floor function.

0 Likes

#5

@kornel I want inheritance since I want cheap access to fields from base class (in my case, DisplayElement). Zero-cost non-dispatch access to fields like x and y (f64 coordinates) is something I want. With traits it’s impossible to access x and y directly; e.g., if you call fn x() it’ll lookup dinamically for the struct holding x. That’s why I want inheritance.

Now, enums are an approximation (since you can declare a struct DisplayElement holding a inner enum like WhichElement::Sprite, but they’re not what I want since it should be possible to treat DisplayElement as Sprite and vice-versa. enums are also fixed-size and all these element kinds will be reference types, not stack-based structs.

@vitalyd fn x(&self) -> f64, it’s a field stored by Element, however inside the underlying Rc pointer. That field should be accessed directly by types such as Canvas, Sprite and so on.

@trentj I meant 1st-grade inheritance with structures. E.g., you declare a class Element and inherit it with sub-types Sprite and Canvas, for instance, but you can’t inherit these sub-types (e.g. Main < Sprite) nor can you inherit Element after declaring it with the macro (class_decl!).

// Element is sealed/final in outer crates. 
class Element {
    double x;
    double y;
}

sealed class Sprite : Element {}

sealed class MovieClip : Element {}

var mc = new MovieClip;
mc.x;
MovieClip(mc).x;
0 Likes

#6

Well, it’s not impossible to elide the function call, especially when you are not using trait objects. If a function is small enough, which it will be for getting a field value, then the LLVM optimizer will get rid of the function call and just inline it. You could also mark the function with a #[inline] to hint the LLVM to inline it.

As @kornel said before, Rust does not have inheritance, the closest approximation is Deref coercions, but it is an anti-pattern to use Deref coercions to approximate inheritence.

0 Likes

#7

If you have lots of elements with positions, usually the fastest way is to replace array of structures with a structure of arrays, so you have Vec<XYPositions> and Vec<OtherData>. This way your physics code can be maximally efficient by using only one of the arrays. See ECS pattern.

For more classical approach, in Rust, it’s done with encapsulation. You’d have struct Sprite {element: Element} and use sprite.element.x or pass &sprite.element to functions.

0 Likes

#8

This is because more things fit in the cache if each thing is smaller, if you’re interested.

0 Likes