How to share same implementation and maybe share fields - struct

How can I simplify this code? I still can't wrap my head around rust's traits and structs after OOP.
struct Player {
entity: Entity,
hp: i32,
atk: i32
}
struct Chest {
entity: Entity,
amount: i32
}
impl Drawable for Chest {
fn draw(&self, mut pencil: Pencil) {
pencil.draw(&self.entity);
}
}
impl Drawable for Player {
fn draw(&self, mut pencil: Pencil) {
pencil.draw(&self.entity);
}
}
Maybe there is a way to inherit some fields like in OOP?
Also, if anybody knows a good and clear tutorial about traits and structs in rust, I would be very glad if you shared it!

In my experience, typically when you want to "share attributes" like this you typically want to break the structs into their own types and implement the traits you need on each.
Consider if your structs looked like this:
struct DrawableEntity {
entity: Entity,
... // other stuff you might need to draw
}
struct Chest {
drawable: DrawableEntity,
...
}
struct Player {
drawable: DrawableEntity,
...
}
impl Drawable for DrawableEntity { ... }
Then in your code it might look like this:
player.drawable.draw(mut pencil);
chest.drawable.draw(mut pencil);

Since your impl blocks do the exact same thing with a different type, you can abstract that out into a macro so that is is trivial to repeat for any new Drawable types:
macro_rules! impl_drawable {
($t:ty) => {
impl Drawable for $t {
fn draw(&self, mut pencil: Pencil) {
pencil.draw(&self.entity);
}
}
};
}
impl_drawable!(Chest);
impl_drawable!(Player);
However, that is about all I think you can do, traits can't have fields like an abstract class in an OOP language would, so you can't really "share" the idea of having an Entity in a struct.
If you haven't read this already, the book has a chapter that talks a bit about common OOP patterns and how they translate to idiomatic Rust.

Related

How to program shared behaviors in Rust without repeating same code in each module?

For writing a very large program, I see no way to alleviate having to write the same code for each struct that uses a certain shared behaviour.
For example, Dog may "bark":
struct Dog {
is_barking: bool,
....
}
impl Dog {
pub fn bark(self) {
self.is_barking = true;
emit_sound("b");
emit_sound("a");
emit_sound("r");
emit_sound("k");
self.is_barking = false;
}
....
}
And many breeds of this dog may exist:
struct Poodle {
unique_poodle_val: &str
}
impl Poodle {
pub fn unique_behaviour(self) {
self.some_behaviour();
}
}
struct Rottweiler {
unique_rottweiler_val: u32
}
impl Rottweiler{
pub fn unique_behaviour(self) {
self.some_behaviour();
}
}
The issue is that Rust seems incapable of this in my current knowledge, but it needs to be done and I need a workaround for:
Allowing Poodle and Rottweiler to bark using the exact same behavior which the breeds should not need to regard.
Allowing this to be possible without recoding bark() in every breed module, which is programming hell as it leads to repetitious code and every module has to implement bark().
Traits are the inverse and cannot access the struct, so default-trait implements do not work. Rust does not support OOP-like inheritance and nor is it desired here.
Therefore, I ask:
How would it be possible to implement bark() without rewriting bark() in each module, since Poodle and Rottweiler bark exactly the same way after all?
Please provide an example of how to solve this issue in Rust code, idiomatic and slightly hacky solutions are welcome but please state which they are as I am still learning Rust.
Thank you.
Edit: The boolean is not a thread thing, rather it's a example of setting some state before doing something, i.e. emit_sound is within this state. Any code we put into bark() has the same issue. It's the access to the struct variables that is impossible with say, traits.
You've put the finger on something that Rust doesn't do nicely today: concisely add to a struct a behavior based on both internal data and a function.
The most typical way of solving it would probably be to isolate Barking in a struct owned by all dogs (when in doubt, prefer composition over inheritance):
pub struct Barking {
is_barking: bool,
}
impl Barking {
pub fn do_it(&mut self) {
self.is_barking = true; // <- this makes no sense in rust, due to the ownership model
println!("bark");
println!("a");
println!("r");
println!("k");
self.is_barking = false;
}
}
struct Poodle {
unique_poodle_val: String,
barking: Barking,
}
impl Poodle {
pub fn unique_behaviour(self) {
println!("some_behaviour");
}
pub fn bark(&mut self) {
self.barking.do_it();
}
}
struct Rottweiler {
unique_rottweiler_val: u32,
barking: Barking,
}
impl Rottweiler{
pub fn unique_behaviour(self) {
println!("unique behavior");
}
pub fn bark(&mut self) {
// maybe decide to bite instead
self.barking.do_it();
}
}
In some cases it can make sense to define a Barking trait, with a common implementation and declaring some functions to deal with the state:
pub trait Barking {
fn bark(&mut self) {
self.set_barking(true);
println!("bark");
println!("a");
println!("r");
println!("k");
self.set_barking(false);
}
fn set_barking(&mut self, b: bool);
}
struct Poodle {
unique_poodle_val: String,
is_barking: bool,
}
impl Poodle {
pub fn unique_behaviour(self) {
println!("some_behaviour");
}
}
impl Barking for Poodle {
fn set_barking(&mut self, b: bool) {
self.is_barking = b;
}
}
Beware that this half-OOP approach often ends up too much complex and less maintainable (like inheritance in OOP languages).

Zero cost builder pattern for recursive data structure using transmute. Is this safe? Is there a better approach?

I would like to create a struct using the builder pattern which must be validated before construction, and I would like to minimize the construction overhead.
I've come up with a nice way to do that using std::mem::transmute, but I'm far from confident that this approach is really safe, or that it's the best approach.
Here's my code: (Rust Playground)
#[derive(Debug)]
pub struct ValidStruct {
items: Vec<ValidStruct>
}
#[derive(Debug)]
pub struct Builder {
pub items: Vec<Builder>
}
#[derive(Debug)]
pub struct InvalidStructError {}
impl Builder {
pub fn new() -> Self {
Self { items: vec![] }
}
pub fn is_valid(&self) -> bool {
self.items.len() % 2 == 1
}
pub fn build(self) -> Result<ValidStruct, InvalidStructError> {
if !self.is_valid() {
return Err(InvalidStructError {});
}
unsafe {
Ok(std::mem::transmute::<Builder, ValidStruct>(self))
}
}
}
fn main() {
let mut builder = Builder::new();
builder.items.push(Builder::new());
let my_struct = builder.build().unwrap();
println!("{:?}", my_struct)
}
So, this seems to work. I think it should be safe because I know the two structs are identical. Am I missing anything? Could this actually cause problems somehow, or is there a cleaner/better approach available?
You can't normally transmute between different structures just because they seem to have the same fields in the same order, because the compiler might change that. You can avoid the risk by forcing the memory layout but you're then fighting the compiler and preventing optimizations. This approach isn't usually recommended and is, in my opinion, not needed here.
What you want is to have
a recursive data structure with public fields so that you can easily build it
an identical structure, built from the first one but with no public access and only built after validation of the first one
And you want to avoid useless copies for performance reasons.
What I suggest is to have a wrapper class. This makes sense because wrapping a struct in another one is totally costless in Rust.
You could thus have
/// This is the "Builder" struct
pub struct Data {
pub items: Vec<Data>,
}
pub struct ValidStruct {
data: Data, // no public access here
}
impl Data {
pub fn build(self) -> Result<ValidStruct, InvalidStructError> {
if !self.is_valid() {
return Err(InvalidStructError {});
}
Ok(Self{ data })
}
}
(alternatively, you could declare a struct Builder as a wrapper of Data too but with a public access to its field)

What is an idiomatic way to have multiple structs with the same properties in Rust?

I'm aware that Rust does not have inheritance and that the language provides and easy way to share different implementations of the same methods across objects through the use of Traits. But is there an idiomatic way to share property name definitions or do they need to be defined on each struct?
My use case is that I have many different structs that track some information. Each piece of information can be updated and I want each struct to know the date of its last update. Is there a common pattern (maybe macros?) to add a last_update property to all the structs or must I add it to each struct explicitly?
There is currently no way to do this via traits, the closest thing is the "Fields in Traits" RFC (discussion, RFC), but that doesn't seem terribly active as of now.
The simplest way to do this is to have a type / struct with a method and include that field in any struct you want:
struct UpdateTimestamp {
timestamp: Timestamp, // dummy type
}
impl UpdateTimestamp {
fn update(&mut self) {
self.timestamp = now(); // dummy function
}
fn last_updated(&self) -> Timestamp {
self.timestamp
}
}
You could then include this in any struct where you want the functionality:
struct MyStruct {
my_field: u32,
my_other_field: i32,
update_ts: UpdateTimestamp,
}
impl MyStruct {
fn my_field(&self) -> u32 {
// Getter - no update
self.my_field
}
fn set_my_field(&mut self, my_field: u32) {
self.update_ts.update();
self.my_field = my_field;
}
fn last_updated(&self) -> Timestamp {
self.update_ts.last_updated()
}
}
Now you could write a complicated macro for this which automates the implementation part (injects updates into the setters and the last_updated method in the impl block), but unless you're doing this a lot I don't think it would be worth it.

Polymorphism in Rust: Canonical way [duplicate]

I have various structs that all implement the same trait. I want to branch on some condition, deciding at runtime which of those structs to instantiate. Then, regardless of which branch I followed, I want to call methods from that trait.
Is this possible in Rust? I'm hoping to achieve something like the following (which does not compile):
trait Barks {
fn bark(&self);
}
struct Dog;
impl Barks for Dog {
fn bark(&self) {
println!("Yip.");
}
}
struct Wolf;
impl Barks for Wolf {
fn bark(&self) {
println!("WOOF!");
}
}
fn main() {
let animal: Barks;
if 1 == 2 {
animal = Dog;
} else {
animal = Wolf;
}
animal.bark();
}
Yes, but not that easily. What you've written there is that animal should be a variable of type Barks, but Barks is a trait; a description of an interface. Traits don't have a statically-defined size, since a type of any size could come along and impl Barks. The compiler has no idea how big to make animal.
What you need to do is add a layer of indirection. In this case, you can use Box, although you can also use things like Rc or plain references:
fn main() {
let animal: Box<dyn Barks>;
if 1 == 2 {
animal = Box::new(Dog);
} else {
animal = Box::new(Wolf);
}
animal.bark();
}
Here, I'm allocating the Dog or Wolf on the heap, then casting that up to a Box<dyn Barks>. This is kind of like casting an object to an interface in something like C# or Java, or casting a Dog* to a Barks* in C++.
An entirely different approach you could also use would be enums. You could have enum Animal { Dog, Wolf } then define an impl Animal { fn bark(&self) { ... } }. Depends on whether you need a completely open-ended set of animals and/or multiple traits.
Finally, note that "kind of" above. There are various things that don't work as they would in Java/C#/C++. For example, Rust doesn't have downcasting (you can't go from Box<dyn Barks> back to Box<Dog>, or from one trait to another). Also, this only works if the trait is "object safe" (no generics, no using self or Self by-value).
DK has a good explanation, I'll just chime in with an example where we allocate the Dog or Wolf on the stack, avoiding a heap allocation:
fn main() {
let dog;
let wolf;
let animal: &dyn Barks = if 1 == 2 {
dog = Dog;
&dog
} else {
wolf = Wolf;
&wolf
};
animal.bark();
}
It's a bit ugly, but the references accomplish the same indirection as a Box with a smidge less overhead.
See also:
How can I conditionally provide a default reference without performing unnecessary computation when it isn't used?
How do I make format! return a &str from a conditional expression?
Defining a custom enumeration is the most efficient way to do this. This will allow you to allocate on the stack exactly the amount of space you need, i.e. the size of the largest option, plus 1 extra byte to track which option is stored. It also allows direct access without a level of indirection, unlike solutions using a Box or a trait reference.
Unfortunately, it does require more boiler-plate:
enum WolfOrDog {
IsDog(Dog),
IsWolf(Wolf)
}
use WolfOrDog::*;
impl Barks for WolfOrDog {
fn bark(&self) {
match *self {
IsDog(ref d) => d.bark(),
IsWolf(ref w) => w.bark()
}
}
}
fn main() {
let animal: WolfOrDog;
if 1 == 2 {
animal = IsDog(Dog);
} else {
animal = IsWolf(Wolf);
}
animal.bark();
}
In main we use only a single stack allocated variable, holding an instance of our custom enumeration.

How to typecast and inherit Rust structs?

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).

Resources