As someone fairly new to Rust but experienced in C++, I find Rust's dynamic polymorphism and OOP features rather cumbersome to use. Specifically, I'm talking about:
Runtime downcasting
This can currently be done using Any
, however, you need to modify the trait to allow downcasting from it, and the downcasting syntax is rather verbose:
trait Trait {
fn as_any(&self) -> &'static dyn Any;
}
fn as_type<T: Trait>(object: &'static dyn Trait) -> Option<&'static T> {
object.as_any().downcast_ref::<T>()
}
I don't see an implementation problem with this, as C++'s dynamic_cast
does essentially the same without any trickery like Any
and making traits dynamic_cast
-able explicitly .
Boxing by multiple dyn traits
The following does not work, except for auto traits like Send
and Sync
:
trait FooTrait {
fn as_any(&self) -> &'static dyn Any;
}
trait BarTrait {
fn as_any(&self) -> &'static dyn Any;
}
// error[E0225]: only auto traits can be used as additional traits
fn multiple_traits(object: Box<dyn FooTrait + BarTrait>) {}
I know this can be worked around by creating an aggregate trait that encompasses both FooTrait
and BarTrait
, but unless you have a meaningful connection between the two traits (i.e. Rectangle + Rhomboid
really makes a Square
), this is just littering the code.
I'm not certain about the compiler implementation for this, as this is a bit different than multiple inheritance, but at first glance the semantics seem really straightforward.
Object-safe trait restrictions
You cannot currently have a trait like this in dyn
contexts as it's not object safe:
trait JSON
where Self: Sized
{
fn to_json_str(&self) -> String;
fn parse_json_str(s: &str) -> Option<Self>;
fn to_json_obj<Object: JSON>(&self) -> Option<Object> {
let s = self.to_json_str();
Object::parse_json_str(s.as_str())
}
}
I think this could be worked around by splitting it into 3 traits and implementing ToJSONObj
for dyn ToJSONStr
, but that's again a lot of hassle and code littering.
This also doesn't seem to have implementation hurdles. In C++, it's perfectly legitimate to have template methods and static methods alongside virtual methods in an interface class, because C++ handles object-safety at the method level, not at the class level.
What is Rust's philosophy and future?
Due to the above, I find writing dynamic polymorphism and OOP code in Rust clumsy and unproductive. I wonder, why is this the case? Is it because Rust is a fairly new language and the OOP aspect hasn't gotten too much love yet, or is it somehow against Rust's philosophy to write such code in the first place? But if it's against the philosophy, then why does Rust seem to have the basic building blocks, just not the polish? Am I perhaps following the wrong approach to writing OOP code in Rust?