Repository navigation
Description
When a markdown link is too long to fit on a line of its own, wrap_comments breaks
inside the link destination if that destination contains a @ or a /. The result is
a different document: links that resolved before formatting are unresolved after it.
rustfmt already handles this correctly in two cases — it leaves the line over
comment_width rather than break the link — but which case you get depends on whether
the destination happens to contain a character rustfmt treats as a break opportunity.
Version
rustfmt 1.11.0-nightly (6f5da406b5 2026-10-10)
Also reproduced on rustfmt 1.9.0-nightly (c397dae808 2026-07-02).
Not applicable to stable: wrap_comments is nightly-only, so stable warns
can't set `wrap_comments = true` and leaves the file untouched.
Configuration
edition = "2024"
unstable_features = true
wrap_comments = truecomment_width and max_width are the defaults, 80 and 100.
Reproduction
repro.rs:
pub fn queue_user_requested_email_otp() {}
pub fn queue_user_requested_email_otp_abc() {}
pub struct QueueUserRequestedEmailOtp;
pub mod email {
/// A. [`queue_user_requested_email_otp`](fn@super::queue_user_requested_email_otp), over the channel.
pub fn a() {}
/// B. [`queue_user_requested_email_otp`](struct@super::QueueUserRequestedEmailOtp), over the channel.
pub fn b() {}
/// C. [`queue_user_requested_email_otp`](super::queue_user_requested_email_otp_abc), over the channel.
pub fn c() {}
/// D. [`queue_user_requested_email_otp`](some/path/queue_user_requested_email_otp.html), over it.
pub fn d() {}
/// E. [`queue_user_requested_email_otp`](https://example.com/queue_user_requested_email), over it.
pub fn e() {}
}Every link resolves as written:
$ rustdoc --edition 2024 -D rustdoc::broken_intra_doc_links repro.rs
$ echo $?
0After rustfmt --edition 2024 repro.rs, it does not:
$ rustdoc --edition 2024 -D rustdoc::broken_intra_doc_links repro.rs
error: unresolved link to `queue_user_requested_email_otp`
--> repro.rs:6:14
|
6 | /// A. [`queue_user_requested_email_otp`](fn@
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `queue_user_requested_email_otp` in scope
...
error: aborting due to 3 previous errors
$ echo $?
1Actual
A and B break immediately after the disambiguator's @, and D after a /:
/// A. [`queue_user_requested_email_otp`](fn@
/// super::queue_user_requested_email_otp), over the channel.
/// B. [`queue_user_requested_email_otp`](struct@
/// super::QueueUserRequestedEmailOtp), over the channel.
/// D. [`queue_user_requested_email_otp`](some/path/
/// queue_user_requested_email_otp.html), over it.
C and E are left untouched, at 107 and 103 columns respectively, and both still
resolve.
Expected
All five behave as C and E do: the destination is never split, and the line is allowed
to exceed comment_width when there is nowhere legal to break.
Why the five differ
The destinations that survive are the ones rustfmt finds no break opportunity in. A
:: path has none, so C survives incidentally rather than by rule; E survives by the
explicit URL rule. @ and / are break opportunities, so A, B and D are split.
The protection therefore keys on the URL scheme rather than on being inside ](…).
Every rustdoc disambiguator is affected — fn@, struct@, mod@, macro@, value@,
type@, prim@ — as is any relative path.
Relation to earlier issues
-
wrap_comments breaks links with long URL #3787 (long
https://URLs) — still fixed; case E. -
bug: wrap_comments mangles reference-style links #5095 (reference-style links) — still fixed, and reference-style definitions are the
workaround for this report. -
wrap_comments breaks local links #4130 (
wrap_commentsbreaks local links) — closed 2020-04-19 as already fixed by
fix not to break relative path link #4042, with the reporter confirming at the time. Its example reproduces verbatim on
today's nightly:/// [some_local_link](../super_package/module1/module2/module3/module4/struct.SomeStruct.html) fn some_fn() {}
becomes
/// [some_local_link](../super_package/module1/module2/module3/module4/struct. /// SomeStruct.html)
I have not checked whether fix not to break relative path link #4042 covered this position or only a link at the start of
a line, so this may be a narrower fix rather than a regression.
Impact
A project that runs both checks gets cargo fmt and cargo doc contradicting each
other on the same toolchain: the link on one line fails fmt --check, and rustfmt's
wrapping of it fails the doc check. For an inline link with a disambiguator there is no
spelling that satisfies both.
Workaround
A reference-style definition, which #5096 keeps whole:
/// The twin of [`the email operation`], over the other channel.
///
/// [`the email operation`]: fn@super::queue_user_requested_email_otp
Activity
jieyouxu commentedon Oct 12, 2026
The
@is rustdoc syntax, indeed we can't break on the disambiguators. cf. https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html. NB if someone wants to look at this:@, but in that case, should likely follow rustdoc intra-doc link constraints and be conservative.I suspect the most robust scheme is the most conservative one, i.e. do not break for the links (URIs, relative or absolute paths, or rustdoc intra-doc links).