OOP in Rust and shared/mutable references - oop

In the context of a series of programming lessons, I have decided to use Rust instead of C++ as the support programming language.
One aspect of these lessons is dedicated to OOP (dynamic dispatch) relying on interfaces (dyn traits): composition of minimal interfaces instead of deep inheritance trees.
I know OOP does not fit well with modern languages and approaches, but the existing codebase and the habits of the teams since the 90s are still so present that the students must be at least aware of this paradigm (even if we don't encourage its usage for new developments).
In this playground is shown a minimal example inspired from an exercise formerly done in C++ (many other things exist around this excerpt).
At the abstract level, an Entity has an internal state (a position here, to keep it simple) and several dynamic components responsible for various behaviours (drawing, animation, reaction to events...).
These dynamic components implement some predefined interfaces (dyn traits) and can be freely defined at the application level (the abstract level does not have to know the details of these components).
Some of these components can have some internal data which could even be mutated.
For example, in this minimal code, a Shape component if mainly dedicated to drawing (no mutable operation is required for the entity or this component in general), but an Animator component can cause mutations on the entity (let's say its position), on the component itself and even on other components (change the color of the next drawing for example).
As requested in a comment, here is the code inline:
mod common {
pub trait Shape {
fn draw(
&self,
entity: &Entity,
);
fn change_color(
&mut self,
color: String,
);
}
pub trait Animator {
fn animate(
&mut self,
entity: &mut Entity,
);
}
#[derive(Debug)]
pub struct Pos {
pub x: f64,
pub y: f64,
}
pub struct Entity {
pos: Pos,
shape: Box<dyn Shape>,
animator: Box<dyn Animator>,
}
impl Entity {
pub fn new(
pos: Pos,
shape: Box<dyn Shape>,
animator: Box<dyn Animator>,
) -> Self {
Self {
pos,
shape,
animator,
}
}
pub fn pos(&self) -> &Pos {
&self.pos
}
pub fn pos_mut(&mut self) -> &mut Pos {
&mut self.pos
}
pub fn change_color(
&mut self,
color: String,
) {
self.shape.change_color(color);
}
pub fn draw(&self) {
self.shape.draw(self);
}
pub fn animate(&mut self) {
let anim = &mut self.animator;
anim.animate(self);
}
}
}
mod custom {
use super::common::{Animator, Entity, Shape};
pub struct MyShape {
color: String,
}
impl MyShape {
pub fn new(color: String) -> Self {
Self { color }
}
}
impl Shape for MyShape {
fn draw(
&self,
entity: &Entity,
) {
println!("draw at {:?} with {:?}", entity.pos(), self.color);
}
fn change_color(
&mut self,
color: String,
) {
self.color = color;
}
}
pub struct MyAnim {
count: i32,
}
impl MyAnim {
pub fn new() -> Self {
Self { count: 0 }
}
}
impl Animator for MyAnim {
fn animate(
&mut self,
entity: &mut Entity,
) {
let pos = entity.pos_mut();
if (self.count % 2) == 0 {
pos.x += 0.1;
pos.y += 0.2;
} else {
pos.x += 0.2;
pos.y += 0.1;
}
self.count += 1;
if self.count >= 3 {
entity.change_color("red".to_owned());
}
}
}
}
fn main() {
use common::{Entity, Pos};
use custom::{MyAnim, MyShape};
let mut entity = Entity::new(
Pos { x: 0.0, y: 0.0 },
Box::new(MyShape::new("green".to_owned())),
Box::new(MyAnim::new()),
);
entity.draw();
for _ in 0..5 {
entity.animate();
entity.draw();
}
}
As you can see, the provided code cannot be compiled since, at line 66, anim is a mutable reference to the Animator component responsible for the dynamic dispatch but the parameter of the method is also a mutable reference to the Entity as a whole which contains the previous Animator.
This parameter is needed if we want the Animator to be able to make changes on the entity.
I'm stuck with this situation and I can only think about workarounds that look quite ugly to me:
don't pass the entity as a parameter but each of its field (except the animator) as many parameters: what's the point of defining structs then? (if an entity is made of twelve fields, should I pass eleven parameters every time I would act on this entity?)
embed each field of an entity in a RefCell and pretend every parameter of every function is a non-mutable reference, then borrow_mut() everywhere we want to and hope it won't panic: for me, it's like giving-up the idea that function prototypes tell and enforce the intent of the code (let's add some Rc everywhere in order to totally forget who owns what, and we obtain Java ;^)
I'm certain I did some bad choices about what deserves to be exclusive (&mut) or shared (&), but I can't see a reasonable limit.
In my opinion, when an entity has to be animated, it's its own concern: there is nothing to be shared, except looking at the state of the surrounding environment (but not changing it).
If we share everything and rely on interior-mutability in order to enable safe mutations at run-time (thanks to the ref-count) it sounds to me like: «let's drive like crazy, as if there were no traffic regulations, as long as no one complains (try_borrow()/try_borrow_mut()) and we don't have any accident (panic!())».
Could anyone suggest a better organisation of my structs/functions in order to enable the intended behaviour: an entity made of a few dynamic (as in OOP) components responsible for the details of the actions on the concerned entity?

Related

OOP is discouraged in Rust but there don't seem to be a lot of alternatives for it in 3d applications

I am trying to write a 3d application in rust and since I don't know any suitable alternatives I am currently "approximating" some java like classes using enums.
What I have looks something like this:
enum SceneObj {
Plane(Plane),
Sphere(Sphere),
}
Where Plane and Sphere are both structs. The this is necessary since I want to sore a vec of scene objects.
The problem now arises when I try to, for example, get the position of one of these scene objects. I always have to do something like this:
fn get_scobj_pos(obj: SceneObj) -> Vector {
match obj {
SceneObj::Plane(p) => { p.get_pos() }
SceneObj::Sphere(s) => { s.get_pos() }
}
}
This is really annoying and gets very verbose quickly. It also seems very redundant since all scene objects implement the same trait with these functions.
If I was using a language like Java, C# etc. I could just make the parent class Object and have Plane and Sphere inherit from it.
My question essentially boils down to: is there an alternative solution other than enums. More esoteric alternatives to OOP in 3d programming are also very welcome.
Prefer composition over inheritance, then use helper traits to access different parts of your composed objects more easily. In your case you could do something like:
struct Vector {}
struct BaseObject {
pos: Vector,
}
trait AsBaseObject {
fn as_base_object (&self) -> &BaseObject;
fn get_pos (&self) -> &Vector {
&self.as_base_object().pos
}
}
impl AsBaseObject for BaseObject {
fn as_base_object (&self) -> &BaseObject { self }
}
struct Plane {
base: BaseObject,
}
impl AsBaseObject for Plane {
fn as_base_object (&self) -> &BaseObject {
&self.base
}
}
struct Sphere {
base: BaseObject,
}
impl AsBaseObject for Sphere {
fn as_base_object (&self) -> &BaseObject {
&self.base
}
}
Playground
You may want to create a trait SceneObj, then use Box<dyn SceneObj> if you plan on owning and storing the objects.
Traits are similar to interfaces in Java.
trait SceneObj {
fn get_pos(&self) -> &Vector;
}
struct Plane {
pos: Vector
}
impl SceneObj for Plane { //~ class Plane implements SceneObj
fn get_pos(&self) -> &Vector { //~ #Override Vector getPos()
&self.pos
}
}
But you should avoid using Box<dyn Trait> carelessly.

Designing a UI element tree structure in Rust

I've stumbled upon a somewhat quirky situation due to lack of inheritance in Rust.
I'm designing an element structure where all elements have some common behaviour (e.g. having children) and need to propagate function calls down the tree.
My current design is to have a View trait and a ViewInner struct that encapsulates all common behaviour. However it appears that doing so would spawn tons of boilerplate code that forwards calls from View to its ViewInner. And as it appears, adding another level of encapsulation (e.g. AutoLayoutView) would triple the call depth.
Here's a short example:
// "Base" view trait
pub trait View {
fn render(&self, ...);
fn handle_events(&self, ...);
}
// View internals
pub struct ViewInner {
children: Vec<Box<View>>
}
impl ViewInner {
...
pub fn add_child(...);
pub fn layout(...);
}
// Forwarded calls
impl View for ViewInner {
fn render(&self, ...) {
for child in self.children.iter() {
child.render(...);
}
}
fn handle_events(&self, ...) {
for child in self.children.iter() {
child.handle_events(...);
}
}
}
// Actual entity
pub struct SomeView {
inner: ViewInner,
...
}
impl SomeView {
pub fn new() -> Self {
return Self {
inner: ViewInner::new()
}
}
}
impl View for SomeView {
// Forwarding all over again
fn render(&self, ...) {
self.inner.render(...)
}
fn handle_events(&self, ...) {
self.inner.handle_events(...)
}
}
What would be a more optimal way to structure this and minimize boilerplate in the future (in case of more tree-crawling calls and View variations)?
For the generic cases where the calls will be forwarded without any added logic you can write a custom derive macro for the View trait.
With it you just need to write:
#[derive(View)]
pub struct ViewInner {
...
}
and the View trait will be automatically implemented for the struct.

Implement fmt::Display for Vec<T>

I want to implement the fmt::Display for a nested struct commonly used in my code.
// The root structure
pub struct WhisperFile<'a> {
pub path: &'a str,
pub handle: RefCell<File>,
pub header: Header
}
pub struct Header{
pub metadata: metadata::Metadata,
pub archive_infos: Vec<archive_info::ArchiveInfo>
}
pub struct Metadata {
// SNIP
}
pub struct ArchiveInfo {
// SNIP
}
As you can see, this is a non-trivial tree of data. The archive_infos property on Header can be quite long when presented as one line.
I would like to emit something along the lines of
WhisperFile ({PATH})
Metadata
...
ArchiveInfo (0)
...
ArchiveInfo (N)
...
But when I try to display Vec<ArchiveInfo> I get that Display is not implemented. I can implement fmt::Display for ArchiveInfo but that's not enough since fmt::Display is not implemented for the parent container Vec. If I implement fmt::Display for collections::vec::Vec<ArchiveInfo> I get the impl does not reference any types defined in this crate; only traits defined in the current crate can be implemented for arbitrary types.
I have tried iterating over the vec and calling write!() but I couldn't figure out what the control flow should look like. I think write!() needs to be return value of the function but that breaks down with multiple calls.
How can I pretty print a Vec of my structures?
As this error states, you cannot implement a trait for a type you don't own:
the impl does not reference any types defined in this crate; only traits defined in the current crate can be implemented for arbitrary types
However, you can implement Display for your wrapper type. The piece you are missing is to use the try! macro or the try operator ?:
use std::fmt;
struct Foo(Vec<u8>);
impl fmt::Display for Foo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Values:\n")?;
for v in &self.0 {
write!(f, "\t{}", v)?;
}
Ok(())
}
}
fn main() {
let f = Foo(vec![42]);
println!("{}", f);
}

Traits and implementations in Rust

I have the following types:
trait Monster {
fn attack(&self);
fn new(int) -> Self;
}
struct CookiesMonster {
cookies: int,
hungry_level: int,
}
impl Monster for CookiesMonster {
fn new(i: int) -> CookiesMonster {
CookiesMonster { cookies: i, hungry_level: i + 1 }
}
fn attack(&self) {
println!("I have {:d} cookies!!", self.cookies)
}
}
struct Dummy {
count: int
}
impl Dummy {
fn new(i: int) -> Dummy {
Dummy { count: i }
}
}
Now, this works:
let monster: CookiesMonster = Monster::new(10);
let dummy = Dummy::new(10);
But this doesn't:
let monster = CookiesMonster::new(10);
Why can't I call the new method directly on the CookiesMonster type?
Because that's how traits work at the moment. Static methods in traits must be called on the trait, and not on the implementor of the trait.
Note that calling the methods on the trait instead of on the type implementing the trait allows cases like this to be unambiguous: Consider if you added the following code to your example:
trait Newable {
fn new(int) -> Self;
}
impl Newable for CookiesMonster {
fn new(i: int) -> CookiesMonster {
CookiesMonster { cookies: i, hungry_level: 0 }
}
}
In this context, Monster::new still works, but CookiesMonster::new would be ambiguous.
(In this example, it figures how which implementation of the trait to use based on type inference. A generalized syntax such as Trait::<for Type>::static_method has been discussed as a way to explicitly write down one's intentions, but I am not sure how far along that is.)
Update circa 15 July 2014: the "Unified Function Call Syntax" proposal tracks the work alluded to in the previous paragraph. See Rust RFC PR 132. My understanding is that UFCS as described in that RFC would actually allow for you to write CookiesMonster::new when Monster is the only trait in scope that both (1.) provides the new method and (2.) is unambiguously implemented for CookiesMonster.

Objects and classes in Rust

I am fiddling around with Rust, going by the examples, trying to make a class. I have been looking at the example of StatusLineText
It keeps raising the errors:
error: `self` is not available in a static method. Maybe a `self` argument is missing? [E0424]
self.id + self.extra
^~~~
error: no method named `get_total` found for type `main::Thing` in the current scope
println!("the thing's total is {}", my_thing.get_total());
^~~~~~~~~
My code is rather simple:
fn main() {
struct Thing {
id: i8,
extra: i8,
}
impl Thing {
pub fn new() -> Thing {
Thing { id: 3, extra: 2 }
}
pub fn get_total() -> i8 {
self.id + self.extra
}
}
let my_thing = Thing::new();
println!("the thing's total is {}", my_thing.get_total());
}
You need to add an explicit self parameter to make methods:
fn get_total(&self) -> i8 {
self.id + self.extra
}
Functions without the explicit self parameter are considered associated functions, which can be called without a specific instance.