Organizing main struct as basket::fruit::Fruit or basket::Fruit?

Hi folks

I noticed lints.clippy linter module_name_repetitions that warns when for example an enum is somemodule::SomeModuleName (it should rather be somemodule::Name).

And I thought if I have the following structs:

basket::fruit::Fruit
basket::fruit::FruitApple
basket::fruit::FruitOrange

I should rename it to:

basket::fruit::Fruit
basket::fruit::Apple
basket::fruit::Orange

Or maybe I should also move the main Fruit struct from the fruit module:

basket::Fruit
basket::fruit::Apple
basket::fruit::Orange

And then import two "things" instead of one like this use basket::{Fruit, fruit}.

What do you think?

Thank you.

Peter

There are of course a lot of options to choose from. Clippy's lints tend to be opinions. It's beneficial to have opinions, but they can change depending on context as well as several unrelated factors.

One option to consider is restructuring so that you don't have any Fruit intermediate type for holding things in a Basket. Perhaps the basket can also hold vegetables, tubers, fungi, and nuts. Maybe it can hold inedible items like flowers, clothes, and tools.

Categorizing these might be useful for storing them in other kinds of bins: Refrigerator could be a good place to store fruits, but not clothes. But neither Basket nor Refrigerator need an intermediate type for this categorization. The storage types themselves are metonymously categorical:

struct Basket {
    apples: Vec<Apple>,
    potatoes: Vec<Potato>,
    clothes: Vec<Clothes>,
    hammers: Vec<Hammer>,
    // ...
}

struct Refrigerator {
    apples: Vec<Apple>,
    potatoes: Vec<Potato>,
    // ...
}

enum Apple {
    GoldenDelicious,
    GrannySmith,
    Macintosh,
    // ...
}

enum Clothes {
    Pants,
    Shirt,
    Sock,
    // ...
}

enum Hammer {
    BallPeen,
    Claw,
    Sledge,
    // ...
}

enum Potato {
    Russet,
    Sweet,
    YukonGold,
    // ...
}

I decided to subdivide Apple into varieties of apples, and chose to do the same with Hammer.

They could just as well be simple unit structs: struct Apple; and struct Hammer; if they need no further classification, of course. And then the inner Vec can be simplified to just a counter, since all Apples will be alike and all Hammers will be alike.

I think this is a good illustration that there are a lot of approaches to organizing your data model. And the best way really depends on the functional requirements of the project.

It should be noted that you may disagree with Clippy, and that's ok! You are encouraged to #[allow(clippy:...)] or configure lints in your Cargo.toml to suit your needs.

Hope this is helpful guidance. But it's just, like, my opinion, man.

3 Likes

Thank you.

So which one is more idiomatic?

This:

basket::fruit::Fruit
basket::fruit::Apple
basket::fruit::Orange

Or this one:

basket::Fruit
basket::fruit::Apple
basket::fruit::Orange

For a real-world example take a look at how tokio organizes the sync module (docs). The submodules have Sender and Receiver types but there are types at the top level as well (e.g.Barrier).

I think in your fruit example, submodules would be useful if you wanted to write apple::Seed and orange::Seed. What is the fruit module doing for you here?

The clippy warning would complain about many types and traits in std (Option, Result, Error, etc.)

1 Like

Thank you.

I'm parsing data and building a struct with nested structs. It's a tree of modules and each module has one or multipe parsers.

I think those are OK (for example std::option::Option). That linter would compain if there was a struct or enum use std::option::OptionFooBar.