Skip to content

Support grouping all builder types under a separate module #214

@Veetaha

Description

@Veetaha

Problem Statement

The original conversation about this happened in ayrat555/frankenstein#231 (comment).

The problem is that builders are sometimes more of a noise in the docs. Having builder types appear in the docs adjacently to other more important types doesn't always make sense.

It may make sense to group builder types under a separate module in large-scale scenarios. These are cases that involve hundreds of structs with #[derive(Builder)].

Solution

We need a solution for this that would generate a module structure like the following one:

pub mod builders {
    pub use super::internal::{FooBuilder, /* ... */};
}

pub use internal::{Foo, /* ... */};

mod internal {
    #[derive(Builder)]
    pub struct Foo { /**/ }
    
    /* ... */
}

How can we achieve this? I see several ways to tackle this problem

Workaround

It's possible to achieve this in the current state of bon (3.0.2 at the time of this writing) with the following declarative macro (it requires paste and macro_rules_attribute):

macro_rules! builders {
    (
        mod __inline__ {
            $(
                $(#[$attr:meta])*
                pub struct $name:ident { $($body:tt)* }
            )*
        }
    ) => {
        pub mod builders {
            ::paste::paste! {
                #[doc(inline)]
                pub use super::internal::{ $( [<$name Builder>], )* };
            }
        }

        #[doc(inline)]
        pub use internal::{ $($name,)* };

        mod internal {
            use super::*;
            $(
                #[derive(bon::Builder)]
                $(#[$attr])*
                pub struct $name { $($body)* }
            )*
        }
    };
}

#[macro_rules_attribute::apply(builders!)]
mod __inline__ {
    pub struct Foo { /**/ }
}

Here __inline__ isn't anything special. We just need to give a name to the module the builders macro is applied to. That module will be flattened by the macro. In theory, a cleaner solution would be to make it into a function-like macro builders! { }, but rustfmt doesn't format code inside of {} of the function-like macro, unfortunately.

Official macro?

There could an official proc macro attribute that would be more robust than the workaround declarative macro suggested higher. It would handle non-struct items (e.g. functions and methods). It would also accept variable visibility for items (not just pub), and would provide better syntax errors handling for better IDE experience.

Codegen?

People could use external code generation CLI, that would directly create a Rust file with the structs with builder derives. That would be very ad-hoc and inconvenient, since one would need to keep the generated code fresh in git sources.

Is there enough demand for this to provide an official API for this?

I'm not sure how I feel about exposing a builders macros from bon yet. This isn't something any builder crate has done yet in the history of ~8 years (derive_builder age). Maybe it's not as big of a problem, so that people just didn't solve it. Also, the workaround with the declarative macro may be just good enough for a solution here.

I'm ready to be proven wrong though. I'd just like to avoid exposing API that would be very limited, which isn't particularly nice at the call site (due to the nesting and the dummy module name __inline__).

A note for the community from the maintainers

Please vote on this issue by adding a 👍 reaction to help the maintainers with prioritizing it. You may add a comment describing your real use case related to this issue for us to better understand the problem domain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    design neededThe feature requires more design effortfeature requestA new feature is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions