-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multi-part examples in rustdoc #3081
base: master
Are you sure you want to change the base?
Conversation
I personally don't think this is a needed addition: it seems to be a bit too much specific to be really useful (or used). I'm interested to see what the others in the @rust-lang/rustdoc team think about it though. |
Between parts, any markdown may appear, including code blocks. | ||
However, no interleaving or nesting of multi-part examples is allowed. | ||
|
||
Not only _rust_ examples may be multi-part. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you indicate that? split-start,rust
or rust,split-start
or something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code blocks are rust
by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean rust
as a generic language tag — I could have been clearer by saying split-start,javascript
. I think I feel a bit confused about why it says "Not only rust examples may be multi-part."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does 592ede1 resolve your concerns?
One future possibility is that code editors would support this. | ||
Assuming, of course, that they would support language features inside of code examples in documentation, at all. | ||
|
||
We expect early adoption of this feature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
One could claim that this RFC allows code examples to be written in [literate programming][literate_programming] style. | ||
|
||
We are not familiar with a feature equivalent to the doc-tests in other languages. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doctests are pretty common.
- Python
- Ruby
- Elixir
- D
- Racket has runtime contracts that are part of the documentation: https://blog.racket-lang.org/2012/06/submodules.html#in-source-documentation.
It would be good to know if any of them have interleaved tests like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the links to doc-tests in other languages. Actually, our paragraph wasn't clear.
We know that doc-tests is a common feature.
What we were trying to say is that we're not aware of are features in other languages that are equivalent to the one we are suggesting in this RFC.
Corrected with 9992d9f.
We hope that the community could refer us to any of those.
Note that for an equivalent feature to be really equivalent, it needs to:
- Generate readable documentation, such as HTML from source code documentation
- Extract tests from examples in that source code documentation
- Support some rich text format interleaved with code lines of those examples
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to know if any of them have interleaved tests like this.
This is supported in Python where you can write a docstring like this:
"""
First code block:
>>> x = 10
Second code block:
>>> x
10
"""
I think this is useful, although it does stretch the scope of rustdoc a bit. I'd like to hear from library authors, since they'll be the ones using it - @Darksonn @BurntSushi @dtolnay would you be interested in this? |
I do sometimes write things in this style, but I have not found the need to do so in our API reference. The examples there are not really complicated enough to warrant it in my experience. Maybe it would be more useful in mdBook? |
I would love to have this for Bevy! Actually demonstrating functionality can involve quite lengthy, multipart code, and we rely heavily on our stand-alone and docs-integrated examples as integration tests. |
## Why error on inconsistent language tags | ||
|
||
From the guide section earlier: | ||
|
||
> If a language tag is present, it must be the first word in the info-string and must be present in all parts of the multi-part example. | ||
|
||
An alternative to this is that specifying the language in the `split-start` block suffices and rustdoc can understand that all following parts of the multi-part example are of the same language. | ||
There is a problem with that. | ||
Pure markdown doesn't know and will never know anything about this feature. | ||
Markdown only [acknowledges][commonmark-fenced] the _typical_ use of the first word to specify a language: | ||
|
||
> The first word of the info string is typically used to specify the language of the code sample, and rendered in the class attribute of the code tag. However, this spec does not mandate any particular treatment of the info string. | ||
|
||
So any tools that are not rust-specific (or that don't yet implement multi-part examples) would not have the same understanding of this feature as rustdoc will, and therefore will think that only the first code block is whatever language was specified and the rest of the code blocks are not necessarily the same language. | ||
|
||
Erroring when the language tag varies between parts of a multi-part example prevents this situation. | ||
|
||
At the very least, it would probably prevent some authors from a future moment where they would think to themselves "oh, darn — I wish _I had_ placed the `ruby` tag on _every part_ of the multi-part example". | ||
|
||
Since the majority of multi-part examples are expected to be rust code, where the `rust` langauge tag is usually omitted, this error is not expected to be common. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jyn514 continuing from #3081 (comment) we were on the fence about this. I, personally, don't like the feature of rust
being default, for the same compatibility concern. I guess that's where I am coming from in this.
We wouldn't mind if it is deemed more desirable that subsequent code blocks inherit the language of the first one.
If you ask me, I feel it is consistent with a design feature I don't agree with, making things only slightly worse.
Another point is that this inheritance would take a little bit more implementation work, wouldn't it? Not sure it's a relevant point or not. If there is difference in implementation, perhaps we could first error and then re-visit this inheritance later, if anyone really cares.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also continuing #3081 (comment).
@@ -72,6 +72,10 @@ If `split-start` / `split-continue` / `split-end` blocks appear in an erroneous | |||
# Reference-level explanation | |||
[reference-level-explanation]: #reference-level-explanation | |||
|
|||
UI Mockup: | |||
|
|||
![UI mockup](./mockup.png) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has two different 'Run' buttons, one on each code block. Would that run the full example in both cases? If not, does it make sense to only add it to the second block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I have looked into this RFC more, I am not certain about a very relevant point: those run buttons — where do I know them from? Where are they currently implemented? Is it in rustdoc or in mdBook? The only place I have certainly seen them is the standard library documentation.
This point is relevant because if they are not a rustdoc feature, then it would seem of no concern to this RFC and the mockup should not include such buttons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is part of rustdoc. I think you need the #![doc(html_playground_url)]
attribute for them to show up: https://github.com/rust-lang/rust/blob/4c55c8362de32be1530b2441c3e3a51e73edeb21/library/core/src/lib.rs#L58
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also implemented in mdBook, but as a button that looks (roughly) like this: ⏯️
I wonder what @carols10cents thinks of this RFC. I have not read it yet, but this feature in general feels like something that we may want to take advantage of in the book. I know at various times I've wanted something like this in long-form writing about Rust. |
This is a really nice-to-have feature. |
Doesn't the Book use mdBook though? |
@camelid unless something has changed (or i am 1000% misremembering), mdbook calls out to rustdoc. |
Ah, I forgot that mdBook uses rustdoc for tests 👍 |
So yes, the book does use mdBook, and mdBook takes Rust code blocks from the markdown files and then runs rustdoc test on them. So it's kind of backwards from library documentation. When we have long examples in the book that we explain in pieces, I'm using the I believe there are a few cases where one code file has multiple anchors in it, so that we are accomplishing the same goal that is here of having text interleaved with multiple parts of one long code example. But most of the time, in the book, we're helping the reader to build up an example from nothing, and it's not often straightforward like "add lines 3 and 4, then add lines 5, 6, and 7". Usually it's "we're going to write a function that returns a static value, ok now we're going to take an argument, ok now we're going to change the return type, and now we have to change the call sites too". So it's more applying changes to one example to build it up, rather than explaining it line-by-line. So I don't think this feature as written would be relevant to the book, is what I'm trying to get at. Now, for library documentation, I personally find it very difficult to write long doc comments anyway, to get them to show up where I think people would look for them in the moment they need them. And writing markdown inside So overall, I'm not sure that adding this feature to rustdoc would be useful. I do, however, appreciate the thought that has gone into this RFC and appreciate the desire to make docs better! |
As a library author that writes lots of examples, I'm not sure I would have much use for something like this personally. While I'm not quite as pessimistic about writing code inside of With respect to the UI mockup, I have two comments/questions:
|
Prompted by @carols10cents' comment, I realized that I would be much more inclined to write longer form documentation1 if the effort involved with publishing a book was reduced. To that end, I'm suggesting that docs.rs build and host mdBook content. That may be a complete alternative to this RFC. 1 — And I already try to write longer form documentation inside of my more user-facing crates. For example, SNAFU has an entire |
Co-authored-by: Arif Driessen <[email protected]>
Co-authored-by: mbartlett21 <[email protected]>
081dfd7
to
033c4b2
Compare
|
||
One could claim that this RFC allows code examples to be written in [literate programming][literate_programming] style. | ||
|
||
We are not aware of a similar feature in any other language. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python has a long history of using doctests: comments with embedded Python interpreter sessions. They look like this:
This is an example text file in reStructuredText format. First import
``factorial`` from the ``example`` module:
>>> from example import factorial
Now use it:
>>> factorial(6)
120
Notice how the two >>>
lines share state: they're part of one interpreter session. The example is from the doctest
documentation. This style of comment was supported at least 20 years ago as seen in PEP-287.
The Python doctest library has been ported to other languages, such as Haskell. The documentation for that library explains that it clears the state between each code block by default (but there is a --fast
flag which disables this since there is a performance cost to clearing the state).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we just concatenate code blocks within a single doc comment?
It seems like most code blocks within a doc comment are in the context of the item being documented.
/// We can bind a value to a variable
/// ```
/// let x = 0;
/// ```
/// We can use the bound variable as a value in another binding
/// ```
/// let y = x;
/// ```
/// We can use `shadowing` to bind a new variable with the name of an existing variable
/// ```
/// let x = 5;
/// ```
/// Note that the original variable `x` is still in scope, but can no longer be directly accessed
This would compile to:
let x = 0;
let y = x;
let x = 5;
For the most part, it seems variable shadowing can easily be used to soft-reset variable names without resetting the entire scope. And if users are really particular about their scope they can be explicit with braces (with or without #).
/// ```
/// # {
/// let x = 0;
/// ```
/// ```
/// let y = x;
/// # }
/// ```
/// ```
/// let x = 5;
/// ```
Are there prior discussions with reasoning for avoiding this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the suggestion, @beeryt. Do you feel that implicit concatenation of code blocks fits in with other design decisions in Rust?
For one, that would be a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with prior design decisions, but here is an addendum to my suggestion to possibly avoid a breaking change:
Add a #[doc]
attribute like test(combine_code_blocks)
.
#[doc(test(combine_code_blocks))]
/// ```
/// let x = 0;
/// ```
/// ```
/// let y = x;
/// ```
I'm not sure whether this would be better as an item-level (#[doc...]
) or crate-level (#![doc...]
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Combining by default would break std
and quite a huge amount of crates. I don't think an attribute is the right way to do it either. Using codeblock labels seems like a better idea:
/// ```combine
/// let x = 0;
/// ```
///
/// blabla
///
/// ```
/// let x = 1;
/// ```
///
/// ```combine
/// assert!(x == 0);
/// ```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mentioned yourself that an attribute would break std docs. So from that, I don't think this approach is worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would only break std if the attribute was added to std, right? I was thinking this was an approach which would avoid breaking existing docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. Sorry. I badly exposed what I had in mind. What I meant is that if you opt in this attribute, either on an item or not, then you remove the possibility to have different examples and combined examples in the same docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a combination of my attribute and codeblock labels could work. I'm just brainstorming ideas that are ideally less verbose in both scenarios. I imagine it is less common for someone to explicitly want separate scopes for every code block while also wanting some separation.
#[doc(test(combine_code_blocks))]
/// ```
/// let x = 0;
/// ```
/// ```
/// let y = x;
/// ```
/// ```new_scope
/// let x = 5;
/// ```
/// Below will `panic!`
/// ```
/// assert_eq!(x, 0);
/// ```
Maybe (and I don't know if the label syntax would support this) allowing us to set named scopes could work:
#[doc(test(combine_code_blocks))]
/// ```
/// let x = 0;
/// let y = x;
/// ```
/// ```set_scope(name_or_index)
/// let x = 5;
/// ```
/// Below will pass because it is part of the *default* scope
/// ```
/// assert_eq!(x, 0);
/// ```
This example would create two scopes for execution:
// *default* scope
{
let x = 0;
let y = x;
assert_eq!(x, 0);
}
// scope(name_or_index)
{
let x = 5;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we just concatenate code blocks within a single doc comment?
It seems like most code blocks within a doc comment are in the context of the item being documented.
Yes, I had the same idea — and I think it would work very well. The stdlib examples show that it sometimes won't work, but from my experience, it's very rare to include full main
functions in doctests, especially since the point of a doctest is to be lightweight.
My proposal would be to implement it conservatively by concatenating the code blocks per item. If this doesn't compile, then try again without concatenating.
That would allow all code to keep working.
We could perhaps remove the fallback in a new edition? I know the standard library has to be compatible across editions, but we could just rewrite the doctests to avoid the breakage.
As alternative, we could have people mark code blocks now if they want them to be concatenated and then make this a default later.
I've been missing this a lot: I come from Python where doctests are widely supported. Doctests in Python allow you to save state between code blocks and this is very useful to be able to show a continued worked examples. Here is a random example from Scipy where they first define a matrix and then verify some property of it in the next code block: Note how the second code block uses |
@arifd
Co-authored-by: Arif Driessen [email protected]