Pre-RFC: allow to merge inherent impl blocks in the HTML

Summary

Allow to merge various impl blocks in the HTML output

Motivation

There is only one usecase in mind – currently RustDoc generates separate impl docs for every impl block. Example:

Screenshot

Implementing this RFC would allow to merge said blocks into one by using new #[doc(merge)] attribute (they will be separate if said block won't be specified).

Guide-level explanation

I guess it would go there – The #[doc] attribute - The rustdoc book. Something along the lines of:


At the item level

merge

Docs of Inherent Implementations annotated with #[doc(merge)] attribute will be merged into one impl Ty block.

#[doc(merge)]
impl Foo {}

#[doc(merge)]
impl Foo {}

Drawbacks

I can't really think of anything. Feel free to suggest.

Rationale and Alternatives

Leaking implementation details into user docs is silly! To elaborate:

If a lot of macros are involved because of reasons it forces people to write code such as

impl Foo {
    (...)

    make_fn_a!(
    /// Docs docs docs
    );

    make_fn_b!();

    make_fn_c!();
}

which is harder to grasp and reason about than

impl Foo {...}

impl_a!(
/// Docs docs docs
Foo
);

impl_b!(
/// Docs docs docs
Foo
);

impl_c!(Foo);

There are also cases similar to:

impl_some_traits_but_imagine_that_it_generates_fns_in_impl_Bar_block! {
    for Bar {
        Clone => bar_construct_copy;
        Drop => bar_destroy;
        PartialEq => bar_operator_equal;
    }
}

which ends up cluttering docs or actual implementation. Choose yer poison.

EDIT: I think being able to specify doc merge groups could be a very nice feature too:

// Will be merged with other docs with `#[doc(merge)]` attribute.
#[doc(merge)]
impl Foo {...}

// Will be merged with other docs with `#[doc(merge = A)]` attribute.
#[doc(merge = A)]
impl Foo {...}

but I'm unsure if it wouldn't be too messy


:waving_hand: I'm new here, so feel to tell me if I've got something wrong!!

best,

Y

4 Likes

This is a reasonable suggestion but doesn't seem to have generated much enthusiasm. As initial feedback, here are some considerations that should be addressed.

How would doc strings on implementations get merged?

/// A `Foo` does this
#[doc(merge)]
impl Foo {...}

/// A `Foo` does that
#[doc(merge)]
impl Foo {...}

What should be the behavior if some merged impl's contain where clauses or other generic type variations.

#[doc(merge)]
impl<T> Foo<T> {...}

#[doc(merge)]
impl<T> Foo<T> where T: Clone {...}

#[doc(merge)]
impl<T> Foo<Option<T>> {...}
3 Likes

Another question is whether we would eventually want to be able to control what blocks get grouped, rather than consolidating down to only on grouping.

2 Likes

I'd love to see an attribute to group impl blocks into categories, like

#[doc(group = "Operators")]
impl Add for Foo { ... }

#[doc(group = "Operators")]
impl Sub for Foo { ... }

...

Then, given that submodules already provide a natural way to group things, this could be supported to reduce repetition:

#[doc(group = "Operators")]
/// Additional description of the group if needed
mod ops {
    impl Add for Foo { ... }
    impl Sub for Foo { ... }
    ...
}

Oooor, maybe simply

/// # Operators
/// Lorem ipsum
mod ops { ... }
4 Likes

First of all, thank you for feedback!!!

I've been toying with @jdahlstrom suggestion and I think [doc(group = "group name")] is what I've been looking for.

It would allow to split documentation into more context-aware blocks (i.e. – for example I might have methods subarray_shallow and subarray_deep which have same API and follow similar logic – currently I have to double docs or link a section in the header, while grouping would allow me to write everything up-front).

I think that given struct Foo:

/// Doc
///
/// Foo Doc.
pub struct Foo<T> {...}

#[doc(group = "Docs of Foo")]
/// Foo does this and that.
mod some_related_methods_of_foo {
    /// This `Foo` is grouped and does `that`.
    impl<T> Foo<T> {
        pub fn that(&self) {}
    }
}

#[doc(group = "Docs of Foo")]
/// I think Foo will keep doing that.
mod some_following_docs_of_foo {
    /// Also doc
    impl<T> Foo<T>
    where
        T: Clone,
    {
        pub fn bazbar(&self) {}
    }
}

/// This impl remains ungrouped.
impl<T> Foo<T> {
    /// Documented fn.
    pub fn foobazbar(&self) {}
}

#[doc(group = "Another Group")]
/// This impl is another group.
impl<T> Foo<T> {
    /// Documented fn.
    pub fn foobazbar2(&self) {}
}

#[doc(group = "Docs of Foo")]
/// This `Foo` is grouped as well and does `this`.
impl<T> Foo<T> {
    pub fn this(&self) {}
}



/// Another non-merged doc.
impl<T> Foo<T>
where
    T: Clone,
{
    pub fn bazbar2(&self) {}
}

#[doc(group = "Docs of Foo")]
/// Another Doc
impl<T> Foo<Option<T>> {
    pub fn bazbarfoo(&self) {}
}

#[doc(group = "Operators", with_heading)]
/// Additional description of the group if needed
mod ops {
    use super::*;
    use std::ops::{Add, Sub};

    impl<T> Add for Foo<T> {
        type Output = T;

        fn add(self, rhs: Self) -> Self::Output {
            ...
        }
    }
    impl<T> Sub for Foo<T> {
        type Output = T;

        fn sub(self, rhs: Self) -> Self::Output {
            ...
        }
    }
}

/// Notes for Clone.
impl<T> Clone for Foo<T> {
    fn clone(&self) -> Self {
        ...
    }
}

#[doc(group = "Another Group")]
/// Notes for Display.
impl<T> Display for Foo<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        ...
    }
}

#[doc(group = "Another Group")]
/// Notes for Debug.
impl<T> std::fmt::Debug for Foo<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        ...
    }
}

Which currently is displayed as:

Screenshot

Should be display as:

Mockup presenting grouped docs

In other words:

  • Every separate, individual variation would be displayed as new, independent block – i.e. impl Foo<T> would be merged with other impl Foo<T> in the same group, while impl Foo<Option<T>> would only be merged with other Foo<Option<T>>.
  • Ordering remains unspecified.
  • Individual docs over items are being merged section-after-section
  • Grouped items would be displayed first.
  • Items still must fall into their respective categories – i.e. impl Foo grouped with A will remain in Implementations section, impl A for Foo grouped with A will remain in Trait Implementations section.
  • While in mockup user-defined headings replace Implementations and Trait Implementations headings, in final version I would leave all the base headers untouched (exposing user-defined headers with little margin and whatnot).
  • Additonally #[doc(group = "JustMergeTheseImplBlocks", no_heading)] might be exposed as well – which would only merge the same blocks, without creating any headings.

I think it might be confusing – using markdown in impl blocks doesn't have – and shouldn't have – any side effects and docs over mod blocks should follow the very same logic.


I think I would move to initial implementation and see how it fits – I'll try to have something by the middle of December.

EDIT: I was stopped by real-life stuff and maintenance of my lib, I'll probably do something before end of the January :unamused_face:

1 Like