Idea: Concatenate strings in #[doc]

This is actually almost possible today! There's an open issue about this in the rust repository: https://github.com/rust-lang/rust/issues/52607

It turns out that the way rust expands macros, if concat!() is used as an argument to another proc-macro, it will be passed successfully into #[doc = ...], whereas using it directly will trigger the error.

I've worked around this in my projects with the following macro:

/// Declares an item with a doc attribute computed by some macro expression.
/// This allows documentation to be dynamically generated based on input.
/// Necessary to work around https://github.com/rust-lang/rust/issues/52607.
macro_rules! calculated_doc {
    (
        $(
            #[doc = $doc:expr]
            $thing:item
        )*
    ) => (
        $(
            #[doc = $doc]
            $thing
        )*
    );
}

This can be expanded/modified to support arbitrary other doc comments! One way would be to mark the specific one to expand using a special symbol, such as @, and to rely on the fact that item includes arbitrary meta attributes as well, like so:


/// Declares an item with a doc attribute computed by some macro expression.
/// This allows documentation to be dynamically generated based on input.
/// Necessary to work around https://github.com/rust-lang/rust/issues/52607.
macro_rules! calculated_doc {
    (
        $(
            $(#[$m:meta])*
            @#[doc = $doc:expr]
            $thing:item
        )*
    ) => (
        $(
            $(#[$m])*
            #[doc = $doc]
            $thing
        )*
    );
}

This could then be used like this:

macro_rules! impl_function {
    ($name:ident) => {
        calculated_doc! {
            /// # Usage:
            ///
            /// ```
            @#[doc = concat!(" let _ = ", stringify!($name), "();")]
            /// ```
            pub fn $name() {}
        }
    };
}

Hopefully that helps - depending on what you're doing, it might make sense to just use an inner macro particular to your macro - something like this:


macro_rules! impl_function {
    ($name:ident) => {
        impl_function!($name, concat!(" let _ = ", stringify!($name), "();"));
    };
    ($name:ident, $usage:expr) => {
        /// # Usage:
        ///
        /// ```
        #[doc = $usage]
        /// ```
        pub fn $name() {}
    };
}
impl_function!(hi);

This whole thing is definitely a bit hacky, but as there are published crates which inadvertently use this (or purposefully use this), I don't think it'll be removed anytime soon.

1 Like