So, while writing the code within the implementation of structs, a question of coding style came up.
I'd like to know what is the "usual way" for Rustaceans.
If you have a struct with well over a dozen of fns that are not just 3-liners, do you place them each within a new impl Whatever {}?
Or do you even place shorties within their own impl Whatever {}?
I personally like to use different impl blocks. Once I even had these impl blocks separated across files as they could be grouped into three different sections. You just have to own the type, you don't have to place impls next to the struct itself.
I tend to have a small number of impl Whatever blocks, each with lots of functions; it's normal for me to have a single impl Whatever block.
I'll split into multiple blocks for one of three reasons:
It results in the documentation generated by rustdoc being easier to follow.
I'm splitting across multiple files - each file is a different module within the crate, and I'll put a single impl block in each file when I'm splitting this way.
There's a code readability improvement by grouping into different impl blocks.
But, if none of those reasons apply, I'll tend to use one impl block.
I agree. Another reason for multiple impl blocks is to have different trait bounds for different groups of methods, each with their own impl block. I do this rather than put the trait bounds on the individual methods.
Thats the only reason I split them up. I don't really see the readability value of spliting them, in the same file or across multiple ones. It does make some sense if some methods are related to some specific module and you want to group that modules logic, I havent personally done that yet but it makes sense.
Note that rustdoc shows each impl block separately in documentation. You can use this to group methods together for documentation purposes, and to allow you to document the impl block as a whole to explain what this group is.
For example:
/// Ways to frobnicate your Foo
impl Foo {
/// frobnicate like an egg
pub fn frobnicate_like_egg(&mut self) { … }
/// frobnicate like a widget
pub fn frobnicate_like_widget(&mut self) { … }
/// frobnicate like it's 1999
pub fn frobnicate_like_1999(&mut self) { … }
}
/// Things you can do to a region with a Foo in hand
impl Foo {
/// End all wars
pub fn end_all_wars(&self, region: &mut Region) { … }
/// Solve hunger, temporarily
pub fn feed_everyone(&self, region: &mut Region) { … }
/// Reveal truths
pub fn supply_perfect_knowledge(&self, region: &mut Region) { … }
}
This will appear as two impl Foo blocks, each with a comment for the impl block, and methods grouped together, in your rustdoc output.
Thanks a lot for all your input!
This helped a lot to get more structure. Even more than expected.
I like the most @farnz 's approach. Splitting impl into logical blocks helps organize them and makes a better overview. I just tried it. And it failed! But that makes it even better. I tried it with traits. But organizing the traits in the same way by splitting them up is just a logical consequence.
My goal was to reduce all that lengthy stuff going over pages and pages. I don't like that.
So an other contribution is using the editor's code folding. Yes, I know, nothing new. But I rarely used it before.
Now that I started with Rust and a new editor (zed, runs on MacOS and Linux, Windows coming), I'm quite happy. zed's LSP-integration is like magic (for me). It even knows what impls are missing.
Having used sublime before (their LSP integration is more a hack than elegant), VS code's GUI is to ugly for me and I can't get accustomed to vim and the like.
Don't worry. I'll keep reading and listening for further suggestions. I'm learning ...
That's right, you always need a separate impl block when implementing a trait for a given type, and this must implement all non-default methods of the trait and any overridden non-default methods.
So far what was discussed is the ability to have separate impl blocks for the inherent methods of a single struct or enum type. These can be separated for various reasons and they can be in more than one module, unlike the impl block for a trait.
Okay. I see that the example was chosen badly.
It's late here and I am too tired to look for an example where I implemented a feature that is not a trait implementation.
This was really what I was referring to. But again... bad example on my part.