So I have a trait that I want to implement for multiple structs.
The base behavior is really simple but implementing all of it manually
would create lots of unnecessary code and would be incredibly tedious.
Is there a way around implementing it manually every single time?
I know that it's possible with macros but due to the limitation that you need a dedicated crate
for macros, it's quite hard to get them into my existing project.
It's not clear from your example code what exactly you're trying to achieve but I think what you want is to provide a default implementation of those functions in your trait:
which you can then implement and override as necessary:
pub struct SimpleBlock;
impl Block for SimpleBlock { } // uses trait definition defaults
pub struct CoolBlock;
impl Block for CoolBlock { // overrides hello but uses the other defaults
fn hello() -> i8 {
42
}
}
I know that it's possible with macros but due to the limitation that you need a dedicated crate
for macros, it's quite hard to get them into my existing project.
You're probably thinking of procedural macros but for this kind of use case, macro_rules! are much simpler and can be defined inside your existing crate.
I know about default implementations.
The problem is that the code in my actual application is way more complex than just returning an integer.
It also needs a state but an interface can not have fields.
Your impl blocks are “tedious” to write, but not sufficiently similar to use the simple anti-repetition mechanisms like default implementations. It will be hard to suggest an alternative without understanding the similarities and differences between your different objects; can you elaborate on what you’re actually trying to do?
If one trait is a superset of another trait, you can use blanket implementation. I had a case when I had 2 traits and I needed an umbrella trait that encompassed both traits. Blanket implementation made that easy.
I wrote a long rant here but I think I'll save it for an eventual blog post. The gist is: this question feels like an inheritance-shaped hole in a design that probably isn't ideal for Rust. To fix this we need to know two things: what is the program supposed to do, and how does Block fit into a design to achieve that goal?
Thanks for all of your replies.
I'm quite new to rust and sorry if I'm asking silly questions but I think this community is awesome!
A block should represent a section having several sub-blocks.
There should be different blocks having different functionality.
That's why I made a trait called Block and in the real world application
it has parent & children methods.
The different functionalities should be represented by different structs implementing the block trait.
I don't think your questions are silly, on the contrary, they're very important to answer for newcomers to Rust! What's difficult is that they're very open ended and so there are lots of different answers that have various tradeoffs.
Just some really high-level feedback: you've pretty accurately described the design you want but you haven't really described the problem you're trying to solve. What are these blocks? Are they blocks of text? Blocks in a video game? How do they relate to what your program does?
The reason we're asking is because the design you're asking for feels very much like Java style OO which doesn't tend to translate very well to Rust for a variety of reasons. Rather than trying to figure out how to express that design in Rust which is going to cause more problems for you later (wanting parent/child references in some kind of a tree can be quite difficult or at least tedious to make work in Rust), we should help you figure out a more idiomatic design in Rust which solves the actual problem.
Okay, but what do they do? What problem does your program solve and how does Block fit in?
Design starts with requirements. The design of a typesetting engine is going to be different than the design of a natural language processing library, even though both deal with "blocks of text". They have different architectures that are a consequence of different requirements. They solve different problems.
What's the problem your program solves? How does Block relate to other parts of the program's structure?
I'm not an expert by any means but I've found that the tidiest way to do this kind of thing in rust is for the CoolBlock struct to contain a Block, then
let mut my_cool_block = CoolBlock();
... // later
my_cool_block.block.hi()
If CoolBlock.hi() differs from Block.hi() then the function can call the component struct function before or after doing its additional processing. I've found the process of sorting my functionality to enable this to work nicely (c.f. the approach of Java, Python, C#) is very useful.
The example from the page is as simple as it gets:
expansion to a ''println!''
// This is a simple macro named `say_hello`.
macro_rules! say_hello {
// `()` indicates that the macro takes no argument.
() => {
// The macro will expand into the contents of this block.
println!("Hello!");
};
}
fn main() {
// This call will expand into `println!("Hello");`
say_hello!()
}