
Initializing such map, however, could be tricky – the beauty of the implementation above is that it does not require any initialization. It could be a shared map that is somehow dynamically built and then looked up during the casting. In the end, all we need is a mapping from two type identifiers ( TypeId of the concrete type and TypeId of a trait) into a virtual table, corresponding to that combination. This shortcoming could be lifted by implementing a dynamic registry for the implemented traits. So, this is one way you can do casting between traits! One limitation, however, limited in that it requires a 'static bound on the type.Īnother limitation is that all “implemented” interfaces need to be declared upfront (because in this implementation we need to generate a “dispatching” function that should know all supported interfaces). What if we define our own trait, ExtraInfo, like this:
#Rust downcast trait object code#
Therefore, if you have different types of errors, but which have similar “extra” information attached to them (like line and column information, or any other additional detailed information about the error), the code which processes the chain of errors, needs to know all the possible types. One limitation, though, is that you can only cast to a concrete type. This way you can cast an error to something more concrete and retrieve additional information from it (for example, it could be column and line information). If type matches (for example, you have MyError type implementing Fail trait and you are casting Fail trait object to that MyError type), it will return a reference to the concrete type. There is a function on Fail trait, downcast_ref which, given the target type, tries to “cast” the referenced Fail implementor to that type. However, to help with that, Fail trait has a built-in downcasting functionality. Basically, you can go down the chain or convert an error to its string representation. However, there is not much you can do with that Fail trait. Each error, in this case, will be hidden behind a trait object of a Fail trait. This is convenient in higher-level error handling code, for example, to collect all the information down the error chain to present it to the end user or the calling system. This crate provides an error handling abstraction which, among other things, allows to build chains of errors and iterate these chains. One example where this could be useful (which looks reasonable to me, but could be a totally terrible idea!) would be a failure crate. However, what if we want to dispatch a call based on both a type of the interface we want to dispatch on and the concrete type? What does it mean and why would we even want that? Is it a Failure?

Finally, the code would call that function, providing a pointer to the data as the first parameter (which becomes &self in the function). Then, it would lookup the virtual table to get the address of the function to call (each function is assigned a unique slot in the table, so this is a simple indexing operation). The second pointer points to a virtual table where each “slot” is the address of the function.Įvery time Rust compiler generates code to invoke a function on a trait object, the generated code would destructure the trait object into two pointers, the data pointer and virtual table pointer. The first pointer points to the data itself. Again, I am going to skip the details, but the idea is that a trait object is represented internally by two pointers.

The way Rust dynamic dispatch works is via a special reference type, a trait object which provides a way to dispatch a call based on a concrete type. Instead, I would like to do an experiment of making dynamic dispatch even more dynamic! Like in Java 1. I’m going to skip the basics about the traits and just link to another blog post with a good explanation about static and dynamic dispatch in Rust: Traits and Trait Objects in Rust. Systems are logic or behavior that work by iterating over groups of components.In Rust, traits are a powerful tool to use polymorphism, both static and dynamic. There are three main things in the ECS pattern:Īn Entity is a thing that has various Components attached to it.Ĭomponents are just a chunk of data, in Rust we'll use regular structs as our components.įor example a Health component might look like this: struct Health ( i32 ) Our ECS will be very small and do very few things, but it will be an ECS!įirst things first: What is the ECS pattern?ĮCS is a pattern to manage composable logic and shared behavior. I'll explain concepts and syntax that are unique to Rust. This tutorial assumes a little familiarity with Rust, but you'll be able to follow along if you're familiar with similar languages. In this short tutorial we're going to build our own.

The Entity Component System (or ECS) pattern is all the rage in the Rust game development community. Tutorial: Writing a tiny Entity Component System in Rust
