How to typecast and inherit Rust structs? - struct

Note: This was asked about a pre-1.0 version of Rust, involving a feature which has since been withdrawn from the language.
I am using the experimental feature feature(struct_inherit).
I have the LocalPlayer and NetPlayer structs and they both implement the Inputable trait as well as inherit the player_num field from the virtual struct Player. Depending on how a game starts, player_2 in my program can either be a LocalPlayer or NetPlayer. Depending on which one, the way that the Inputable trait is implemented changes.
The compiler won't let me dynamically assign player_2 a type depending on whether it's a NetPlayer or LocalPlayer. It complains:
error: mismatched types: expected ~player::LocalPlayer but found ~player::NetPlayer (expected struct player::LocalPlayer but found struct player::NetPlayer)
It also won't let me typecast NetPlayer or LocalPlayer pointers to a Player pointer. It claims they aren't scalable.
The code sample in question is as follows:
let player1 = box player::LocalPlayer::new(0);
let player2;
if local {
player2 = box player::LocalPlayer::new(1);
} else if hosting {
player2 = box player::NetPlayer::new(1);
player2.host();
} else {
player2 = box player::NetPlayer::new(1);
player2.connect();
}
/* ... Omitted Code ... */
let input = player2.get_input(); // Trait function
The struct implementations are as follows:
pub virtual struct Player {
player_num: uint
}
pub struct LocalPlayer: Player;
pub struct NetPlayer: Player;

Struct inheritance in Rust is very primitive; you probably don't want to use it. There is no subtyping or coercion between inheriting structs in Rust. The feature basically just allows you to save typing if you have a lot of structs with similar fields (it's also future proofing for maybe adding more features around them in the future).
You could make Player, LocalPlayer and NetPlayer traits. You then get the subtyping behaviour you want between them. Alternatively, you could make just LocalPlayer and NetPlayer structs and let Player be a trait. You could even have a PlayerImpl struct which you could inherit from the way you do currently (if you have a lot of fields to share), but you would need to write independent impls for both structs.

I ended up implementing Player as an enum:
pub enum Player {
Net(NetPlayer),
Local(LocalPlayer)
}
Every time I call a shared function, I need to do the following:
let input = match player2 {
player::Net(player) => player.get_input(),
player::Local(player) => player.get_input(),
};

Rust's traits are similar to interfaces, and can be composed to emulate a hierarchy of interfaces. They're often an alternative to inheritance:
trait GetInput {
fn get_input(&self);
}
impl GetInput for Player {
fn get_input(&self) {}
}
impl GetInput for NetPlayer {
fn get_input(&self) {}
}
// Fast: interface selected at compile time
fn do_something<T: GetInput>(player: T) {
player.get_input();
}
// Flexible: interface selected at run time (virtual call)
// Use with Box<Player> (see "Trait Objects")
fn do_something_dyn(player: &dyn GetInput) {
player.get_input();
}
However, Rust doesn't have inheritance of data. To have common fields shared between types, you need some alternative DIY solution (e.g. getters/setters in traits or structs with enums).

Related

How do I avoid Enum + Trait pattern when a struct is not object safe?

I get the implications of object safety, but I'm trying to find an idiomatic way to solve for this situation.
Say I have two structs that share common behavior and also need to derive PartialEq for comparison in another part of the program:
trait Growl:PartialEq {
fn growl(&self);
}
#[derive(PartialEq)]
struct Pikachu;
#[derive(PartialEq)]
struct Porygon;
impl Growl for Pikachu {
fn growl(&self) {
println!("pika");
}
}
impl Growl for Porygon {
fn growl(&self) {
println!("umm.. rawr?");
}
}
In another struct, I want to hold a Vec of these objects. Since I can't use a trait object with Vec<Box<Growl>>...
struct Region{
pokemon: Vec<Box<dyn Growl>>,
}
// ERROR: `Growl` cannot be made into an object
... I need to get more creative. I read this article, which suggests using an enum or changing the trait. I haven't yet explored type erasure, but it seems heavy-handed for my use case. Using an enum like this is what I've ended up doing but it feels unnecessarily complex
enum Pokemon {
Pika(Pikachu),
Pory(Porygon),
}
Someone coming through this code in the future now needs to understand the individual structs, the trait (which provides all functionality for the structs), and the wrapper enum type to make changes.
Is there a better solution for this pattern?
I read this article, which suggests using an enum or changing the trait. I haven't yet explored type erasure, but it seems heavy-handed for my use case.
Type erasure is just a synonym term for dynamic dispatch - even your original Box<dyn Growl> "erases the type" of the Pokemon. What you want here is to continue in the same vein, by creating a new trait better taylored to your use case and providing a blanket implementation of that trait for any type that implements the original trait.
It sounds complex, but it's actually very simple, much simpler than erased-serde, which has to deal with serde's behemoth traits. Let's go through it step by step. First, you create a trait that won't cause issues with dynamic dispatch:
/// Like Growl, but without PartialEq
trait Gnarl {
// here you'd have only the methods which are actually needed by Region::pokemon.
// Let's assume it needs growl().
fn growl(&self);
}
Then, provide a blanket implementation of your new Gnarl trait for all types that implement the original Growl:
impl<T> Gnarl for T
where
T: Growl,
{
fn growl(&self) {
// Here in the implementation `self` is known to implement `Growl`,
// so you can make use of the full `Growl` functionality, *including*
// things not exposed to `Gnarl` like PartialEq
<T as Growl>::growl(self);
}
}
Finally, use the new trait to create type-erased pokemon:
struct Region {
pokemon: Vec<Box<dyn Gnarl>>,
}
fn main() {
let _region = Region {
pokemon: vec![Box::new(Pikachu), Box::new(Porygon)],
};
}
Playground

Downcast trait to generic type

I have the following 2 traits: Org, Capability.
#![feature(specialization)]
trait Org {}
struct OrgV1 {}
impl Org for OrgV1 {...}
struct OrgV2 {}
impl Org for OrgV2 {...}
trait Capability {}
struct CapV1 {}
impl Capability for CapV1 {...}
struct CapV2 {}
impl Capability for CapV2 {...}
// Capability has a generic associated .ensure method that is able to handle
// any combination of the concrete types of Org and Capability.
// It is implemented with rust's specialization feature: https://rust-lang.github.io/rfcs/1210-impl-specialization.html
trait CapabilityService<O: Org> where Self: Capability {
fn ensure(self, org_config: Arc<O>) -> Result<bool>
}
impl<O: Org, C: Capability> CapabilityService<O> for C {
default fn ensure(self, org_config: Arc<O>) -> Result<bool> {
... // not so important logic
return Ok(true)
}
}
fn main() {
...
// assume I have the following 2 variables I got in runtime by serde's deserealization:
// org_trait: instance Org trait object
// capability_trait: instance Capability trait object
// Given that there is .ensure that can handle any combination of the concrete repo and org
// is there a way in rust to downcast to a generic concrete type, aka:
let concrete_org = org_trait.as_any().downcast_ref::<T: Org>().unwrap()
let concrete_capability = capability_trait.as_any().downcast_ref::<T: Capability>().unwrap()
// where concrete_org and concrete_capability are some concrete types of Org,
// and Capability respectively.
let result = concrete_capability.ensure(concrete_org)
}
I am aware that I could do something like this:
{
if let Some(org) = org_trait.as_any().downcast_ref::<OrgV1>() {
if Some(cap) = cap_trait.as_any().downcast_ref::<CapV1>() {
cap.ensure(org)
}
...
} else Some(org) = org_trait.as_any().downcast_ref::<OrgV2>() {
...
}
...
}
But it seems like a nightmare to handle once there are more Org/Cap implementations(in my actual case, I have 3 traits that ensure works on, which I didn't include to make the picture a bit clear)
Is it possible to downcast trait to a generic concrete type, given the constraints(since we know that .ensure would be able to be called no matter what the concrete types are)? I would like to avoid writing my own long chain of nested if let Somes if possible
Thanks all. It seems like I have misunderstood how rust's specialization feature works. Specifically, my thought process was opposite of the note mentioned on the RFCs page: This default impl does not mean that Add is implemented for all Clone data, but just that when you do impl Add and Self: Clone, you can leave off add_assign

rust traits as interfaces to different functions [duplicate]

I have multiple types with similar methods. I want to abstract over them by writing an interface, like I would in Java:
public interface Shape {
public float area();
}
class Circle implements Shape {
public float area() {
return radius * radius * Math.PI;
}
public float radius;
}
However, there is no interface keyword in Rust. Doesn't Rust offer the possibility to abstract over multiple types?
TL;DR: The closest to interface in Rust is a trait. However, do not expect it to be similar in all point to an interface. My answer does not aim to be exhaustive but gives some elements of comparison to those coming from other languages.
If you want an abstraction similar to interface, you need to use Rust's traits:
trait Shape {
fn area(&self) -> f32;
}
struct Circle {
radius: f32,
}
impl Shape for Circle {
fn area(&self) -> f32 {
self.radius.powi(2) * std::f32::consts::PI
}
}
struct Square {
side: f32,
}
impl Shape for Square {
fn area(&self) -> f32 {
self.side.powi(2)
}
}
fn main() {
display_area(&Circle { radius: 1. });
display_area(&Square { side: 1. });
}
fn display_area(shape: &dyn Shape) {
println!("area is {}", shape.area())
}
However, it is an error to see a Rust trait as an equivalent of OOP interface. I will enumerate some particularities of Rust's traits.
Dispatch
In Rust, the dispatch (i.e. using the right data and methods when given a trait) can be done in two ways:
Static dispatch
When a trait is statically dispatched, there is no overhead at runtime. This is an equivalent of C++ templates; but where C++ uses SFINAE, the Rust compiler checks the validity using the "hints" we give to him:
fn display_area(shape: &impl Shape) {
println!("area is {}", shape.area())
}
With impl Shape, we say to the compiler that our function has a generic type parameter that implements Shape, therefore we can use the method Shape::area on our shape.
In this case, like in C++ templates, the compiler will generate a different function for each different type passed in.
Dynamic dispatch
In our first example:
fn display_area(shape: &dyn Shape) {
println!("area is {}", shape.area())
}
the dispatch is dynamic. This is an equivalent to using an interface in C#/Java or an abstract class in C++.
In this case, the compiler does not care about the type of shape. The right thing to do with it will be determined at runtime, usually at a very slight cost.
Separation between data and implementation
As you see, the data is separated from the implementation; like, for example, C# extension methods. Moreover, one of the utilities of a trait is to extend the available methods on a value:
trait Hello {
fn say_hello(&self);
}
impl Hello for &'static str {
fn say_hello(&self) {
println!("Hello, {}!", *self)
}
}
fn main() {
"world".say_hello();
}
A great advantage of this, is that you can implement a trait for a data without modifying the data. In contrast, in classical object oriented languages, you must modify the class to implement another interface. Said otherwise, you can implement your own traits for external data.
This separation is true also at the lowest level. In case of dynamic dispatch, the method is given two pointers: one for the data, and another for the methods (the vtable).
Default implementation
The trait has one more thing than a classic interface: it can provide a default implementation of a method (just like the "defender" method in Java 8). Example:
trait Hello {
fn say_hello(&self) {
println!("Hello there!")
}
}
impl Hello for i32 {}
fn main() {
123.say_hello(); // call default implementation
}
To use classic OOP words, this is like an abstract class without variable members.
No inheritance
The Rust trait's system is not an inheritance system. You cannot try to downcast, for example, or try to cast a reference on a trait to another trait. To get more information about this, see this question about upcasting.
Moreover, you can use the dynamic type to simulate some behavior you want.
While you can simulate the inheritance mechanism in Rust with various tricks, this is a better idea to use idiomatic designs instead of twist the language to a foreign way of thinking that will uselessly make grow the complexity of code.
You should read the chapter about traits in the Rust book to learn more about this topic.

Convenient 'Option<Box<Any>>' access when success is assured?

When writing callbacks for generic interfaces, it can be useful for them to define their own local data which they are responsible for creating and accessing.
In C I would just use a void pointer, C-like example:
struct SomeTool {
int type;
void *custom_data;
};
void invoke(SomeTool *tool) {
StructOnlyForThisTool *data = malloc(sizeof(*data));
/* ... fill in the data ... */
tool.custom_data = custom_data;
}
void execute(SomeTool *tool) {
StructOnlyForThisTool *data = tool.custom_data;
if (data.foo_bar) { /* do something */ }
}
When writing something similar in Rust, replacing void * with Option<Box<Any>>, however I'm finding that accessing the data is unreasonably verbose, eg:
struct SomeTool {
type: i32,
custom_data: Option<Box<Any>>,
};
fn invoke(tool: &mut SomeTool) {
let data = StructOnlyForThisTool { /* my custom data */ }
/* ... fill in the data ... */
tool.custom_data = Some(Box::new(custom_data));
}
fn execute(tool: &mut SomeTool) {
let data = tool.custom_data.as_ref().unwrap().downcast_ref::<StructOnlyForThisTool>().unwrap();
if data.foo_bar { /* do something */ }
}
There is one line here which I'd like to be able to write in a more compact way:
tool.custom_data.as_ref().unwrap().downcast_ref::<StructOnlyForThisTool>().unwrap()
tool.custom_data.as_ref().unwrap().downcast_mut::<StructOnlyForThisTool>().unwrap()
While each method makes sense on its own, in practice it's not something I'd want to write throughout a code-base, and not something I'm going to want to type out often or remember easily.
By convention, the uses of unwrap here aren't dangerous because:
While only some tools define custom data, the ones that do always define it.
When the data is set, by convention the tool only ever sets its own data. So there is no chance of having the wrong data.
Any time these conventions aren't followed, its a bug and should panic.
Given these conventions, and assuming accessing custom-data from a tool is something that's done often - what would be a good way to simplify this expression?
Some possible options:
Remove the Option, just use Box<Any> with Box::new(()) representing None so access can be simplified a little.
Use a macro or function to hide verbosity - passing in the Option<Box<Any>>: will work of course, but prefer not - would use as a last resort.
Add a trait to Option<Box<Any>> which exposes a method such as tool.custom_data.unwrap_box::<StructOnlyForThisTool>() with matching unwrap_box_mut.
Update 1): since asking this question a point I didn't include seems relevant.
There may be multiple callback functions like execute which must all be able to access the custom_data. At the time I didn't think this was important to point out.
Update 2): Wrapping this in a function which takes tool isn't practical, since the borrow checker then prevents further access to members of tool until the cast variable goes out of scope, I found the only reliable way to do this was to write a macro.
If the implementation really only has a single method with a name like execute, that is a strong indication to consider using a closure to capture the implementation data. SomeTool can incorporate an arbitrary callable in a type-erased manner using a boxed FnMut, as shown in this answer. execute() then boils down to invoking the closure stored in the struct field implementation closure using (self.impl_)(). For a more general approach, that will also work when you have more methods on the implementation, read on.
An idiomatic and type-safe equivalent of the type+dataptr C pattern is to store the implementation type and pointer to data together as a trait object. The SomeTool struct can contain a single field, a boxed SomeToolImpl trait object, where the trait specifies tool-specific methods such as execute. This has the following characteristics:
You no longer need an explicit type field because the run-time type information is incorporated in the trait object.
Each tool's implementation of the trait methods can access its own data in a type-safe manner without casts or unwraps. This is because the trait object's vtable automatically invokes the correct function for the correct trait implementation, and it is a compile-time error to try to invoke a different one.
The "fat pointer" representation of the trait object has the same performance characteristics as the type+dataptr pair - for example, the size of SomeTool will be two pointers, and accessing the implementation data will still involve a single pointer dereference.
Here is an example implementation:
struct SomeTool {
impl_: Box<SomeToolImpl>,
}
impl SomeTool {
fn execute(&mut self) {
self.impl_.execute();
}
}
trait SomeToolImpl {
fn execute(&mut self);
}
struct SpecificTool1 {
foo_bar: bool
}
impl SpecificTool1 {
pub fn new(foo_bar: bool) -> SomeTool {
let my_data = SpecificTool1 { foo_bar: foo_bar };
SomeTool { impl_: Box::new(my_data) }
}
}
impl SomeToolImpl for SpecificTool1 {
fn execute(&mut self) {
println!("I am {}", self.foo_bar);
}
}
struct SpecificTool2 {
num: u64
}
impl SpecificTool2 {
pub fn new(num: u64) -> SomeTool {
let my_data = SpecificTool2 { num: num };
SomeTool { impl_: Box::new(my_data) }
}
}
impl SomeToolImpl for SpecificTool2 {
fn execute(&mut self) {
println!("I am {}", self.num);
}
}
pub fn main() {
let mut tool1: SomeTool = SpecificTool1::new(true);
let mut tool2: SomeTool = SpecificTool2::new(42);
tool1.execute();
tool2.execute();
}
Note that, in this design, it doesn't make sense to make implementation an Option because we always associate the tool type with the implementation. While it is perfectly valid to have an implementation without data, it must always have a type associated with it.

Referring to Traits of generic objects seems impossible

Consider the following simple structs:
struct Monster {
// ...
}
struct Player {
// ...
}
struct Missile {
// ...
//target: ???,
}
When writing game logic, it is very common to have objects refer to each other. In the example above, we have the structs Monster, Player and Missile to illustrate the kinds of interactions needed.
Imagine we have the following traits: Position and Think. All the above structs implements Position, and all except Missile implements Think.
The first thing to note is that Missile is a homing missile: It stores a target and whenever the game updates Missile objects, it will move it toward it's target.
As far as I can tell, it is impossible to sensibly store the target of this Missile in Rust.
Obviously, the missile doesn't own its target. It just wants to access its Position trait. The game objects must be shared, through Rc or Gc. But it's not like the Missile can just store a Weak<???> reference to something with a Position trait. A Box<Position> means consuming whatever object had that trait. A Box<Any> does not allow downcasting to traits.
Making Missile<T> and storing the target as Weak<T> doesn't help. How would those Missile<T> be stored? In one Collection<T> for each kind of object the missile targets? Game objects will need to be more generic, and the dreadful Box<Any> seems inevitable.
I'm rather new to Rust. It baffles my mind that this is straight out impossible. Surely, I must be missing something?
I'm also pretty new to Rust, but here's what I've found. You can use the std::rc::Rc<T> struct in combination with Box. To have a mutable shared references, you need to wrap boxed item in a std::cell::RefCell.
So, your player, monster, missile example would go something like this:
use std::cell::RefCell;
use std::rc::Rc;
trait Position {
fn position(&mut self);
}
struct Monster;
impl Position for Monster {
fn position(&mut self) {
println!("Rawr I am getting the monster's position");
}
}
struct Player {
x: i32,
}
impl Position for Player {
fn position(&mut self) {
println!("Getting player's position {}", self.x);
self.x += 1;
}
}
struct Missile {
target: Rc<RefCell<Box<Position>>>,
}
fn main() {
// Create some stuff
let player = Rc::new(RefCell::new(Box::new(Player{x: 42}) as Box<Position>));
let monster = Rc::new(RefCell::new(Box::new(Monster) as Box<Position>));
// Our missile: initial target - monster
let mut missile = Missile{target: monster};
// Should be a monster
missile.target.borrow_mut().position();
// Redirect missile to player
missile.target = player.clone();
// Should be a player
missile.target.borrow_mut().position();
// Show that it is in fact a reference to the original player
player.borrow_mut().position();
}
However, it is usually possible to design entity systems that don't require shared references, and this is considered to be more idiomatic Rust. If your entity system is really complex, I'd recommend using an Entity Component System.
EDIT: Improved code and information, and removed some inaccurate information.

Resources