Skip to content

Commit 917ba2d

Browse files
committed
Initial Rustdoc LaTeX math RFC
1 parent f637718 commit 917ba2d

1 file changed

Lines changed: 317 additions & 0 deletions

File tree

text/3958-rustdoc-texmath.md

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
- Feature Name: `rustdoc_texmath`
2+
- Start Date: (fill me in with today's date, YYYY-MM-DD)
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/3958)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
## Summary
7+
[summary]: #summary
8+
9+
Add support for the de facto standard TeX-math-in-markdown syntax to Rustdoc. It’s currently implemented using the [math-core][] library, which generates MathML Core and is restricted to the subset of LaTeX that can be implemented that way. If you use unsupported syntax, you get a compiler warning from rustdoc.
10+
11+
[math-core]: https://docs.rs/math-core/latest/math_core/
12+
13+
## Motivation
14+
[motivation]: #motivation
15+
16+
It would be nice if we could write complex equations in our docs.
17+
We know that there's demand for this feature,
18+
first of all because people have [asked for it][internals thread],
19+
but mostly because of [crates that did it themselves][] by loading [katex.js][] with inline HTML.
20+
21+
As far as I know, this is the most popular way of doing that:
22+
23+
[package.metadata.docs.rs]
24+
rustdoc-args = ["--html-in-header", "katex-header.html", "--cfg", "docsrs"]
25+
26+
Because only docs.rs reads that directive,
27+
local `cargo doc` and non-Rustdoc doc readers won't see it.
28+
There is a way to make it work in `cargo doc`,
29+
but it [seems to be less popular][include hack].
30+
31+
Fixing this bug and making it easier to build self-contained docs is
32+
the main motivation for adding built-in support for math syntax to Rustdoc,
33+
but there are a few other quality of life improvements that come with this feature:
34+
35+
- We can report math syntax errors on the CLI, just like we do for intra-doc links.
36+
- We can render math in the resulting web page without JavaScript.
37+
No flash of unstyled content or blocking scripts.
38+
- Built-in TeX math doesn't require double-escaping, because the Markdown parser knows about math,
39+
and lets you backslash escape the dollar sign to disable it.
40+
- Cross-crate inlining works.
41+
42+
[internals thread]: https://internals.rust-lang.org/t/adding-latex-support-to-rustdoc/23858
43+
[crates that did it themselves]: https://github.com/search?q=rustdoc-args+%3D+%5B%22--html-in-header%22%2C+%22katex-header.html%22%2C+%22--cfg%22%2C+%22docsrs%22%5D+language%3Atoml&type=code
44+
[katex.js]: https://katex.org/
45+
[include hack]: https://github.com/search?q=%23%21%5Bdoc+%3D+include_str%21%28%22katex.html%22%29%5D+language%3Arust&type=code
46+
47+
## Guide-level explanation
48+
[guide-level-explanation]: #guide-level-explanation
49+
50+
### How to enable
51+
52+
To enable `$\TeX$` math syntax in rustdoc, add this line to your crate root.
53+
54+
#![doc(math_syntax)]
55+
56+
In a future edition, we may enable it by default. If you need to turn it off, add this line to your crate root.
57+
58+
#![doc(no_math_syntax)]
59+
60+
When this feature is enabled, equations are wrapped in single or double `$` dollar signs.
61+
62+
$$\sum_{i=0}^N x_i$$
63+
64+
The result looks like this:
65+
66+
> ![image|138x140](upload://xBv8uwvtJpHGM8ulxeDKRun5vO5.png)
67+
68+
A detailed comparison between our syntax and KaTeX's can be found
69+
[here](https://tmke8.github.io/math-core/comparison.html).
70+
71+
You can add custom \commands by supplying key=value pairs to the math syntax attribute:
72+
73+
#![doc(math_syntax(
74+
// usage: $\floor{x}$
75+
floor=r##"\delim{\lfloor}{#1}{\rfloor}"##,
76+
))]
77+
78+
## Reference-level explanation
79+
[reference-level-explanation]: #reference-level-explanation
80+
81+
### Disabling and enabling math syntax
82+
83+
The crate-level doc attributes `math_syntax` and `no_math_syntax` enable and disable
84+
support for parsing `$`-delimited TeX math in Rustdoc's Markdown.
85+
86+
Obviously, you can't set both of them at the same time. If neither of them are set,
87+
rustdoc will use the edition-specific default (which is currently to disable it).
88+
89+
The `math_syntax` attribute accepts an optional list of `key="value"` pairs for
90+
custom macros. This is similar to the `macros` parameter that KaTeX accepts,
91+
but the `key` part only includes the name of the macro, without the backslash
92+
or the number of parameters. The `"value"` is a string literal with TeX-like code,
93+
and, optionally, `#`numbered parameter placeholders.
94+
95+
### Writing math code in markdown
96+
97+
Math expressions are wrapped in `$` signs. One dollar sign means "inline" math,
98+
and two means "display" math.
99+
100+
Inline math cannot have any whitespace at the start or end of its contents,
101+
so `$1$` is a math span, but `$ 1 $` is not. Inline math spans also
102+
can't be empty.
103+
104+
Display math is allowed to have space at the start,
105+
so `$$ 1 $$` is a display math span.
106+
107+
Unescaped curly braces within math spans must balance,
108+
and unescaped dollar signs can only appear between unescaped curly braces,
109+
so `$$ 1 {$} 2 $$` is parsed as a display math span,
110+
but `$$ 1 $ 2 $$` and `$$ { $$` are not.
111+
112+
### Math syntax
113+
114+
Within a math span, whitespaces are used for grouping and formatting.
115+
But you can't have more than one line break in a row within a math span,
116+
because that ends the paragraph that contains it.
117+
118+
Other characters are usually rendered literally, except for
119+
120+
- backslashes, `\`, which are the sigil for commands
121+
- curly braces, `{` and `}`, which are used for command arguments
122+
- dollar signs, `$`, which delimit math spans
123+
- number signs, `#`, which are used to refer to macro parameters
124+
- ampersands, `&`, which are used for writing matrices and tables
125+
- circumflex, `^`, which is used for exponents
126+
- underscore, `_`, which is used for subscript
127+
- single quote, `'`, which becomes the prime symbol
128+
- tile, `~`, which becomes a rendered, non-breaking space (since ordinary spaces are used for grouping)
129+
- percent, `%`, which mark line comments
130+
- NUL, which is not allowed
131+
132+
Commands are used to write things that can't easily be typed on a keyboard,
133+
and for complex layouts like fractions and matrices. The math-core parser
134+
that we use implements hundreds of commands.
135+
136+
<I>TODO: Full list is in <https://github.com/tmke8/math-core/blob/main/crates/math-core/src/commands.rs>.
137+
Do I need to include it all here?</I>
138+
139+
## Drawbacks
140+
[drawbacks]: #drawbacks
141+
142+
### There is no such thing as invalid Markdown
143+
144+
Adding new syntax to Rustdoc's Markdown is rough,
145+
because it's so difficult to do without causing widespread breakage.
146+
As spelled out in the [CommonMark spec][],
147+
"any sequence of characters is a valid CommonMark document,"
148+
so changing anything so that it acts like a metacharacter where it didn't used to
149+
changes the behavior of already-valid documents;
150+
a *breaking change.*
151+
152+
And, unlike when GitHub redesigned their Markdown as a CommonMark dialect,
153+
we can't run a [one-time batch converter job][] over old crates.io crates [^ghmath].
154+
155+
This class of problem has come up when [intra-doc links were designed][],
156+
when [pulldown-cmark was last updated][],
157+
when [hoedown was replaced with pulldown-cmark in the first place][],
158+
and when [anyone proposes replacing Markdown with something else][]
159+
that has a "principled extension" system.
160+
161+
[^ghmath]:
162+
Did GitHub run a similar batch job when they added math syntax?
163+
I can't think of any reason why they wouldn't, but I also can't find any proof that they did.
164+
It seems like it would require running the math-enabled parser over all the issue comments,
165+
and, if it detects math, add a backslash in front of the dollar signs.
166+
After all, math syntax didn't exist in GitHub Issues until they added it,
167+
so any detected math span is, by definition, a false positive.
168+
169+
[CommonMark spec]: https://spec.commonmark.org/0.31.2/#characters-and-lines
170+
[one-time batch converter job]: https://github.blog/engineering/a-formal-spec-for-github-markdown/#the-migration
171+
[intra-doc links were designed]: https://github.com/rust-lang/rust/issues/54191
172+
[pulldown-cmark was last updated]: https://github.com/rust-lang/rust/pull/121659#issuecomment-1992752820
173+
[hoedown was replaced with pulldown-cmark in the first place]: https://internals.rust-lang.org/t/what-to-do-about-pulldown-and-commonmark/5115
174+
[anyone proposes replacing Markdown with something else]: https://internals.rust-lang.org/t/rustdoc-restructuredtext-vs-markdown/356
175+
176+
### Verbosity or breakage as side effect
177+
178+
From the perspective of 99% of doc authors who didn't want to write a math span in the first place,
179+
false positives that mangle their generated docs are a nasty papercut.
180+
Failing to escape the dollar signs when you needed to is not as bad as [accidentally triggering a link refdef][],
181+
since the degraded result might still be [legible][example rendering of a mistake],
182+
but having to read English text without any spaces sucks.
183+
Also, the LaTeX math syntax is forgiving enough that normal text is often valid,
184+
so Rustdoc compiler warnings won't catch every accidental match.
185+
186+
But if we assume that every doc author adds the escapes that they need,
187+
this forces doc comments to have more escaped metacharacters than they used to.
188+
This makes doc comments less easily readable in their source form,
189+
imposing a cost on the 99% that don't want the feature in favor of the 1% who do.
190+
191+
This argument, if taken to its logical extreme, would imply that we should use plain text
192+
doc comments with no extra formatting features. The downside of doing that is
193+
similar to the downside of not offering TeX math: users who *really* want bold text deploy
194+
[unicode crimes][] and pictures of text, which create accessibility problems.
195+
196+
[accidentally triggering a link refdef]: https://github.com/rust-lang/rust/issues/133150
197+
[example rendering of a mistake]: https://tmke8.github.io/math-core/#input:H4sIAAAAAAAAEwXBwQ0AIQgEwFa2guvCQvaBSoJIhETLv5n2uMIEu6P5MM2JODsF92iVONRBGEseFmsig_79a-7ZDjYAAAA=
198+
[unicode crimes]: https://ux.stackexchange.com/questions/118149/can-screen-readers-interpret-unicode-styles-fonts-such-as-bold-and-italics
199+
200+
## Rationale and alternatives
201+
[rationale-and-alternatives]: #rationale-and-alternatives
202+
203+
### Why TeX math in markdown, specifically?
204+
205+
I would like to avoid the annoying scenario where Rustdoc deploys a complicated, special purpose language, then the community moves on to some new, incompatible language, and we’re stuck maintaining it ourselves because of the stability promise.
206+
207+
There are a lot of special-purpose technical notations that we might theoretically want to support,
208+
but TeX-math-in-markdown is special, for two reasons:
209+
210+
- [Lindy effect][]: LaTeX is an established standard that is not going anywhere any time soon.
211+
- There is more than one implementation of the subset of LaTeX that we need.
212+
213+
The pull request I've been working on uses [math-core][], but, if that implementation turns out
214+
to be problematic, we could pivot to another one, like [pulldown-latex][],
215+
or [katex run in quick-js][] [^1].
216+
That’s not an option with, for example, Typst.
217+
218+
[Lindy effect]: https://en.wikipedia.org/wiki/Lindy_effect
219+
[math-core]: https://github.com/tmke8/math-core
220+
[pulldown-latex]: https://github.com/carloskiki/pulldown-latex
221+
[katex run in quick-js]: https://docs.rs/katex/latest/katex/
222+
223+
[^1]:
224+
I would prefer not to do *that*, because it's slow and seems to have poor error reporting,
225+
but, if we can't achieve good-enough feature support any other way, it's an option.
226+
227+
## Prior art
228+
[prior-art]: #prior-art
229+
230+
- <https://github.com/cben/mathdown/wiki/math-in-markdown>
231+
- <https://en.wikibooks.org/wiki/LaTeX/Mathematics>
232+
- The span parsing is based on the [math spec for commonmark-hs][],
233+
which is the parser used if you run `pandoc` in `gfm` mode.
234+
- Span parsing is documented in more detail in the [math spec for pulldown-cmark][].
235+
236+
[math spec for commonmark-hs]: https://github.com/jgm/commonmark-hs/blob/master/commonmark-extensions/test/math.md
237+
[math spec for pulldown-cmark]: https://pulldown-cmark.github.io/pulldown-cmark/specs/math.html
238+
239+
## Unresolved questions
240+
[unresolved-questions]: #unresolved-questions
241+
242+
### Avoiding Hyrum's Law
243+
244+
There are a lot of \commands in [math-core][], and some of them are known buggy,
245+
meaning they don't match LaTeX itself.
246+
We don't want authors to rely on those bugs, either accidentally
247+
or in a workaround.
248+
249+
Normally, we might "phase in" new commands by making them unstable first,
250+
letting more risk-tolerant authors try it out,
251+
then make it available to everyone else.
252+
But math-core doesn't have an API for that.
253+
254+
### Font
255+
256+
Right now, math formulas default to Noto Sans Math.
257+
258+
This was chosen because it's inoffensive and fine. But it is a sans serif font face, that will usually be surrounded by serif people,
259+
260+
Any suggestions?
261+
262+
263+
## Future possibilities
264+
[future-possibilities]: #future-possibilities
265+
266+
### Undelimited environments
267+
268+
It's a relatively rare feature, but Jupyter Notebook and a few others support
269+
LaTeX environments introduced with the `\begin{foo}` / `\end{foo}` syntax
270+
without wrapping dollar signs.
271+
Since backslashes in Markdown only have meaning when followed by punctuation,
272+
the false positives shouldn't be that common.
273+
Since we don't have to worry about false positives,
274+
we can treat it like a CommonMark block construct and allow blank lines in it.
275+
276+
/// Computes sum from `start` to `end` of the given function.
277+
///
278+
/// \begin{equation}
279+
/// \sum_{i=start}^{end}{f(i)}
280+
/// \end{equation}
281+
fn sum(f: impl FnMut(usize) -> usize, start: usize, end: usize) -> usize {
282+
let mut result = 0;
283+
for i in start..=end {
284+
result += f(i);
285+
}
286+
result
287+
}
288+
289+
### Drawing and charting syntax
290+
291+
There are a lot of different chart formats we *could* try to support.
292+
The tough part is that we want to support it long-term,
293+
give error messages at compile time (if the language has a concept of errors),
294+
and, ideally, have a specification without much churn.
295+
296+
- [PlantUML][] is pretty much exactly what we would want.
297+
But we don't want to bundle a JRE.
298+
- The other obvious choice is [Mermaid][], because GitHub supports it.
299+
The upside is that it's popular and terse. The downside is that the only existing
300+
implementation is a JavaScript library. We could copy in the JS library and embed
301+
the source code into our HTML, but we wouldn't be able to give syntax errors at
302+
Rustdoc compile time that way.
303+
- If we supported undelimited LaTeX environment blocks, then it would make sense to
304+
implement a subset of the [LaTeX drawing tools][] on top of SVG.
305+
The downside is that the only other implementation of these languages that I know of is [LaTeXML][], which is not written in Rust.
306+
The upside is that integrating with the math engine lets you
307+
directly include equations and `math_syntax` macros in your graphics.
308+
- [Svgbob][] actually has a Rust implementation. Ironic, since Svgbob has no syntax errors,
309+
it's actually less important to have a Rust implementation than it is for the others,
310+
which have the possibility of an "invalid document" with errors that we would want
311+
to report at compile time.
312+
313+
[PlantUML]: https://github.com/plantuml/plantuml
314+
[LaTeX drawing tools]: https://en.wikibooks.org/wiki/LaTeX/Introducing_Procedural_Graphics
315+
[LaTeXML]: https://en.wikipedia.org/wiki/LaTeXML
316+
[Mermaid]: https://github.com/mermaid-js/mermaid
317+
[Svgbob]: https://github.com/ivanceras/svgbob

0 commit comments

Comments
 (0)