2

I'm implementing a game using Rust, just to learn the language and some game patterns, and basically I have a concept where I have a Creature type, which can be a Character (a player) or Enemy (the monsters of my game). The way that I'm doing this right now is just declaring Creature as an enum and having two options, Character and Enemy, like this:

pub enum Creature {
    Character(Character),
    Enemy(Enemy),
}

The problem is that in my systems, I always need to add a match to know if it's a character or an enemy, and in most cases I don't care about that, since both have fields in common. So improving this with OOP would be easy, just declare Creature as a class and make Character and Enemy inherit from Creature. But of course this doesn't work in Rust.

So, what I'm trying is to declare Creature as a Trait, and that works for some extent, until I have to type fields as Creature. Basically, I'm sending data over a TCP socket from the server to the client, and serializing the data, and for that, the server message struct must have 'static and implement Send .

So right now I'm getting this error when I try to type a field inside my server message struct as Creature:

the size for values of type `(dyn Creature + 'static)` cannot be known at compilation time
the trait `Sized` is not implemented for `(dyn Creature + 'static)`
no field of an enum variant may have a dynamically sized type
change the field's type to have a statically known size

And my server message looks like:

pub enum ServerMessage {
    ...
    SpawnCreature {
        creature: dyn Creature,
        position: Position,
    },
    ...
}

And in my struct that has the code to send data over the socket, I'm getting this:

`(dyn Creature + 'static)` cannot be sent between threads safely
within `ServerMessage`, the trait `Send` is not implemented for `(dyn Creature + 'static)`
required for `ServerMessage` to implement `PostMsg`

Also, I'm using threads to send and listen for messages in the socket.

So, searching on google I didn't find any good explanation on how I can do this, or if this is even possible, I see some very simple examples using Box, but I don't know if that's going to work for my case.

I know that in Rust I should use composition instead of inheritance, but for this specific case, something like inheritance would improve my code so much.

If there is not really a good way of doing this, it's all good, I'm just curious to know if this is possible in some way.

Gustavo Mendonça
  • 1,925
  • 3
  • 15
  • 26
  • 2
    `dyn Trait` can not exist on the stack directly and must always be behind *some* sort of a pointer, be it a reference or a `Box`. Which one works for you depends on what and how your code is doing, and you don't provide enough info to tell that. – Ivan C Jan 21 '23 at 06:12
  • Possibly relevant: https://stackoverflow.com/a/73163713/5397009 – Jmb Jan 21 '23 at 06:14
  • @IvanC since it must always be behind some sort of a pointer, that doesn't work for me, since I need to send data over the socket and the client must deserialize, I'm not sure how I would achieve this, or if this is even possible. – Gustavo Mendonça Jan 21 '23 at 06:41
  • @Jmb really good topic, but I don't think that solves my issue, but really good example in that answer – Gustavo Mendonça Jan 21 '23 at 06:41
  • You can include serialization/desirialization in your trait, then you will be able to do it. – Ivan C Jan 21 '23 at 06:47
  • I wish rust don't have dyn see https://stackoverflow.com/a/54062081/7076153 – Stargateur Jan 21 '23 at 07:00
  • 1
    *"The problem is that in my systems, I always need to add a match to know if it's a character or an enemy, and in most cases I don't care about that, since both have fields in common."* - in OOP it doesn't matter if they have fields in common, what matters is they can be interacted with the same way. A getter method is just as good as a field for this; so write some getters for your enum type, put your `match` expressions in those, and then every other method can call the getters and there is no need for `match` unless you really want different behaviour for different enum variants. – kaya3 Jan 21 '23 at 07:09
  • 2
    Alternatively, use a struct instead of an enum, put the common fields in the struct, and have one field which is an enum just for the fields that aren't shared between all variants. – kaya3 Jan 21 '23 at 07:11
  • How do you serialize your data before sending it over TCP? – hkBst Jan 21 '23 at 18:25
  • @hkBst I'm using bincode, which just serialize as vec of bytes, and after that I'm compressing using LZ4. – Gustavo Mendonça Jan 21 '23 at 19:06

1 Answers1

0

I think the simplest way is a Creature struct with an enum inside.

struct Creature {
    common_stuff: i32,
    kind: CreatureKind,
}

enum CreatureKind {
    Monster { monster_stuff: i32 },
    Player { player_stuff: i32 },
}

enum ServerMessage {
    SpawnCreature {
        creature: Creature,
    },
}

The alternative where you use a trait object should be possible, but it should know how to serialize itself, if you want to include it as part of a serializable ServerMessage. This seems to involve some complicated type trickery as you can see in this question: How to implement `serde::Serialize` for a boxed trait object?.

hkBst
  • 2,818
  • 10
  • 29