Code
?Sized is unlike other bounds: while T: Foo demands that T implement Foo, T: ?Sized allows T to not be Sized — it does not demand it. It relaxes an obligation, that's it.
If that obligation is demanded by another clause, then adding ?Sized has no effect. For example:
trait Private: Sized {}
pub fn example<T: Private + ?Sized>(value: &T) {}
Here T: Private and Private: Sized imply T: Sized, so T: ?Sized has no effect. This is confirmed by clippy, which raises a needless_maybe_sized lint here.
Since rustdoc is a tool for documenting the public API of Rust programs, I believe that it is misleading and undesirable for it to report T: ?Sized in circumstances where unsized types will not be accepted. It doesn't matter if the definition site of the generic type parameter syntactically mentions T: ?Sized if that clause has no effect on the public API.
Reproduction Steps
cargo doc for HTML output.
RUSTDOCFLAGS="-Z unstable-options --output-format=json --cap-lints=allow" cargo doc for JSON output.
Expected Outcome
Observe pub fn example<T: Private>(value: &T) as the recorded function signature in HTML docs. T: ?Sized does not apply to the API in practice, so it is excluded.
By the same reasoning, do not include T: ?Sized as a trait bound in rustdoc JSON.
Actual Output
Observe pub fn example<T: Private + ?Sized>(value: &T) as the recorded function signature in HTML docs.
Observe the following item in rustdoc JSON:
{
"id": 0,
"crate_id": 0,
"name": "example",
"span": {
// omitted for brevity
},
"visibility": "public",
"docs": null,
"links": {},
"attrs": [],
"deprecation": null,
"inner": {
"function": {
"sig": {
// omitted for brevity
},
"generics": {
"params": [
{
"name": "T",
"kind": {
"type": {
"bounds": [
{
"trait_bound": {
"trait": {
"path": "Private",
"id": 1,
"args": {
"angle_bracketed": {
"args": [],
"constraints": []
}
}
},
"generic_params": [],
"modifier": "none"
}
},
{
"trait_bound": {
"trait": {
"path": "Sized",
"id": 2,
"args": {
"angle_bracketed": {
"args": [],
"constraints": []
}
}
},
"generic_params": [],
"modifier": "maybe" // <-- `?Sized`
}
}
],
"default": null,
"is_synthetic": false
}
}
}
],
"where_predicates": []
},
// omitted for brevity
}
}
}
Version
rustc 1.90.0-nightly (28f1c8079 2025-06-24)
binary: rustc
commit-hash: 28f1c807911c63f08d98e7b468cfcf15a441e34b
commit-date: 2025-06-24
host: x86_64-unknown-linux-gnu
release: 1.90.0-nightly
LLVM version: 20.1.7
@rustbot label +A-rustdoc-json
Code
?Sizedis unlike other bounds: whileT: Foodemands thatTimplementFoo,T: ?SizedallowsTto not beSized— it does not demand it. It relaxes an obligation, that's it.If that obligation is demanded by another clause, then adding
?Sizedhas no effect. For example:Here
T: PrivateandPrivate: SizedimplyT: Sized, soT: ?Sizedhas no effect. This is confirmed byclippy, which raises aneedless_maybe_sizedlint here.Since
rustdocis a tool for documenting the public API of Rust programs, I believe that it is misleading and undesirable for it to reportT: ?Sizedin circumstances where unsized types will not be accepted. It doesn't matter if the definition site of the generic type parameter syntactically mentionsT: ?Sizedif that clause has no effect on the public API.Reproduction Steps
cargo docfor HTML output.RUSTDOCFLAGS="-Z unstable-options --output-format=json --cap-lints=allow" cargo docfor JSON output.Expected Outcome
Observe
pub fn example<T: Private>(value: &T)as the recorded function signature in HTML docs.T: ?Sizeddoes not apply to the API in practice, so it is excluded.By the same reasoning, do not include
T: ?Sizedas a trait bound in rustdoc JSON.Actual Output
Observe
pub fn example<T: Private + ?Sized>(value: &T)as the recorded function signature in HTML docs.Observe the following item in rustdoc JSON:
Version
@rustbot label +A-rustdoc-json