Define rules for summarizing traits and modules

The 1574-more-api-documentation-conventions - The Rust RFC Book mostly focuses on documenting functions. Modules and traits don't get the same attention.

For example, for traits:

from Error in std::error - Rust

Error is a trait representing the basic expectations for error values, i.e., values of type E in Result<T, E> .

from Read in std::io - Rust

The Read trait allows for reading bytes from a source.

from ToString in std::string - Rust

A trait for converting a value to a String .

from Add in std::ops - Rust

The addition operator + .

The first two are consistent with eachother, but it's redundant. The 3rd starts with "A trait", which is still a bit redundant. The 4th goes straight for "The addition operator". None of these seem like particularly good examples of how to write trait docs. The traits in std::convert, aside from AsRef/AsMut which are consistent with eachother, also seem like quite a ride: std::convert - Rust

Module docs also have similar consistency issues. Also, as a side-note, std::iter - Rust says:

Composable external iteration.

However, we've had Iterator in std::iter - Rust and friends for a while now, and those provide internal iteration. (oh yeah and Iterator uses "An interface" as opposed to "A trait"!)

Would it be a bad idea to suggest defining some more conventions about trait and module documentation? Ppl do tend to use std doc guidelines as a starting point for writing their own crate docs, and at least to us it looks like that falls short for traits and modules.

4 Likes

From what I can tell, they don't. You have to explicitly consume an iterator (by calling next on it, or putting it in a for loop, etc.) one-by-one in order to receive its elements. That sounds like external iteration to me.

1 Like

Yeah, "internal iteration" is more akin to a generator imo.

1 Like

Documenting interface types is something I find myself stumbling over in any language, so I would love to have some guidelines in place for traits if only so I don't have to think about the phrasing every single time.

I have tentatively settled on opening trait docs by describing the kinds of types that implement the trait rather than the trait.

Read

A source that allows for reading bytes.

ToString

A value that can be converted to a String.

Add

A value that can be added via the addition operator + .

Error might take some more dramatic rephrasing to fit.

I'm not overly attached to this style (and these examples could probably be improved regardless) but it at least feels less repetitive.

I'm not entirely sure how standardizing module documentation would work. In my experience module docs vary wildly, sometimes being closer to a wiki page than code level documentation. Perhaps there could be some guidelines for common cases where the documentation is short and describes the contents of the module?

4 Likes

Well, at least the summary (the first line, which shows up in module listings and search and whatnot) could have some guidelines. We're not sure how important it is to make those have a consistent format across modules, but we think it would be helpful to guide users who're writing their own docs.

It's not clear from the auto-generated link text, but the link goes to Iterator::for_each, which allows internal iteration if the caller and the iterator both opt into it.

2 Likes

Interesting, they seem to finish the sentence:

A type that implements this trait is...

A source that allows for reading bytes.

A value that can be converted to a String.

A value that can be added via the addition operator + .

Yeah seems to fit.

I like this model because it has the same form as the "commit message" pattern, which is something like A good commit message finishes the sentence: "Applying this commit will...". Not trying to start a bikeshed on commit message phrasing strategies :sweat_smile:, I just thought this was kinda similar.

So then I guess the question is what is the correct start of the sentence that the summary should finish? Is there something better than "A type that implements this trait is..."?

2 Likes

One wrinkle to that way of wording the trait docs is the naming convention. Traits are ideally named by the action they provide, rather than the thing that they are.

E.g. we have Read (not Input, Reader) and Write (not Output, Writer). We have Collect (not Collection). Generally, -able would be considered a subpar trait name per standard name conventions.

This isn't perfectly followed -- Iterator, not Iterate; Future, not Await; Fn, not Call -- but it's a general guideline that traits are functionality/verbs, and structs are things/nouns.

I definitely like the way of presenting the "short first sentence fragment" conventions as "finishes this sentence". It also would implicitly answer the case problem I always run into with e.g. function documentation ("do X" vs "does X") based on the beginning fragment.

1 Like

Hmm I see what you mean, "A Read is a source that allows for reading bytes" is awkward. Is that how people would read the docs on first glance though? If the model is "a fragment that finishes this sentence" people would presumably catch on fairly quickly if there was any consistency across the standard library.

The naming inconsistency is a reasonable concern. Not that the inconsistency is invalid -- there are many reasons for a name besides "would make documentation follow a nice pattern" :laughing: Commit messages don't really have this problem because they aren't subject to interpretation in multiple external contexts.

With that in mind there's nothing saying that the "sentence prefix" must be omitted, you could always just spell it out. This would trade a ittle bit of repetition at the beginning of most summaries for a bit more flexibility when a slightly different phrasing is warranted.

A type implementing Read is a source that allows reading bytes.

A type implementing Write is a sink that allows writing bytes.

A type implementing ToString is convertible to a String.

A type implementing Add can be added via the addition operator +.

A type implementing Iterator enables external iteration of a sequence of elements.

A type implementing Future represents an asynchronous operation.

A type implementing Fn can be called with an immutable receiver.

Here we use that flexibility in order to construct a complete sentence that incorporates the trait name and even references "A type" in the case of ToString. I used the verbs is, is a, can be, enables, represents. I could follow reasonable arguments for three derivations of the above summaries:

  1. The whole quoted sentence. Extremely clear. The prefix clearly indicates that it's a trait. A bit long. Somewhat repetitive (though you don't typically read them all in a row like this).
  2. The suffix starting from the name of the type. Still a complete sentence, loses the unambiguous indication that it refers to a trait.
  3. Just the bolded sections. Shortest. Not a sentence anymore. Could be harder to parse without the implied context of the prefix. Phrasing ends up a little awkward.

I think my favorite is 2. I can understand balking at repeating the name of the type in the summary, though it works great for Go (e.g. template), where the recommendation is this comment is a complete sentence that begins with the name of the element it describes, which is regular enough that it's even checked by default in linters.

It might be worth enumerating potential use-cases / contexts that this summary could be used. Obviously while reading the code or generated docs pages, but it could also be used in an IDE tooltip, etc.

4 Likes

What about Trait lets you...?

Read lets you read bytes from a source.

Add lets you add values together with +.

Iterator lets you iterate values with a for loop.

Sync lets you access values from multiple threads at once.

OsStringExt lets you use additional methods on OsString.

Or even if you don't mention the trait name:

Lets you...

3 Likes

There's actually a pretty important context where you are likely to read a bunch of trait summaries in a row: the module summary pages rustdoc generates. Here the trait name is immediately to the left of the trait summary. This makes any amount of (leading) repetition between traits stick out, and at least in my opinion harder to read. It also means including the type name looks a little silly, though that isn't actually a problem for readability the same way an identical prefix would be.

Skimming the std::io module docs both options 1 and 2 would make trait summaries stick out compared to the docs for the other kinds of items (Cursor and BufReader being the only structs to include the name in the summary)

Given that rustdoc is so widely used I think optimizing summaries for this specific context would be reasonable.

We could also consider changing rustdoc to remove some set of prefixes (such as the type name) when rendering the summary pages, though there are a whole bunch of pitfalls there.

7 Likes

+1 for not reapeating the trait name if possible

BufRead A type of Reader which has an internal buffer, allowing it to perform extra ways of reading.

Read Allows for reading bytes from a source.

Seek Provides a cursor which can be moved within a stream of bytes.

Write Allows for writing bytes into a sink.

1 Like