diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index bdd44b4a99329..b576f28176e5d 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -14,6 +14,7 @@ use rustc_span::Span;
use rustc_span::symbol::{Symbol, sym};
use crate::html::escape::Escape;
+use crate::joined::Joined as _;
#[cfg(test)]
mod tests;
@@ -396,6 +397,8 @@ impl Display<'_> {
sub_cfgs: &[Cfg],
separator: &str,
) -> fmt::Result {
+ use fmt::Display as _;
+
let short_longhand = self.1.is_long() && {
let all_crate_features =
sub_cfgs.iter().all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
@@ -414,20 +417,29 @@ impl Display<'_> {
}
};
- for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
- if i != 0 {
- fmt.write_str(separator)?;
- }
- if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
- if self.1.is_html() {
- write!(fmt, "{feat}
")?;
- } else {
- write!(fmt, "`{feat}`")?;
- }
- } else {
- write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
- }
- }
+ fmt::from_fn(|f| {
+ sub_cfgs
+ .iter()
+ .map(|sub_cfg| {
+ fmt::from_fn(move |fmt| {
+ if let Cfg::Cfg(_, Some(feat)) = sub_cfg
+ && short_longhand
+ {
+ if self.1.is_html() {
+ write!(fmt, "{feat}
")?;
+ } else {
+ write!(fmt, "`{feat}`")?;
+ }
+ } else {
+ write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
+ }
+ Ok(())
+ })
+ })
+ .joined(separator, f)
+ })
+ .fmt(fmt)?;
+
Ok(())
}
}
@@ -439,11 +451,20 @@ impl fmt::Display for Display<'_> {
Cfg::Any(ref sub_cfgs) => {
let separator =
if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
- for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
- fmt.write_str(if i == 0 { "neither " } else { separator })?;
- write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
- }
- Ok(())
+ fmt.write_str("neither ")?;
+
+ sub_cfgs
+ .iter()
+ .map(|sub_cfg| {
+ fmt::from_fn(|fmt| {
+ write_with_opt_paren(
+ fmt,
+ !sub_cfg.is_all(),
+ Display(sub_cfg, self.1),
+ )
+ })
+ })
+ .joined(separator, fmt)
}
ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Display(simple, self.1)),
ref c => write!(fmt, "not ({})", Display(c, self.1)),
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index ffd457c82737c..b6a73602a322f 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -8,12 +8,11 @@
//! not be used external to this module.
use std::borrow::Cow;
-use std::cell::Cell;
use std::cmp::Ordering;
use std::fmt::{self, Display, Write};
use std::iter::{self, once};
-use itertools::Itertools;
+use itertools::Either;
use rustc_abi::ExternAbi;
use rustc_attr_parsing::{ConstStability, StabilityLevel, StableSince};
use rustc_data_structures::captures::Captures;
@@ -35,6 +34,7 @@ use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType;
use crate::html::escape::{Escape, EscapeBodyText};
use crate::html::render::Context;
+use crate::joined::Joined as _;
use crate::passes::collect_intra_doc_links::UrlFragment;
pub(crate) trait Print {
@@ -146,22 +146,6 @@ impl Buffer {
}
}
-pub(crate) fn comma_sep(
- items: impl Iterator- ,
- space_after_comma: bool,
-) -> impl Display {
- let items = Cell::new(Some(items));
- fmt::from_fn(move |f| {
- for (i, item) in items.take().unwrap().enumerate() {
- if i != 0 {
- write!(f, ",{}", if space_after_comma { " " } else { "" })?;
- }
- item.fmt(f)?;
- }
- Ok(())
- })
-}
-
pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>(
bounds: &'a [clean::GenericBound],
cx: &'a Context<'tcx>,
@@ -169,13 +153,11 @@ pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>(
fmt::from_fn(move |f| {
let mut bounds_dup = FxHashSet::default();
- for (i, bound) in bounds.iter().filter(|b| bounds_dup.insert(*b)).enumerate() {
- if i > 0 {
- f.write_str(" + ")?;
- }
- bound.print(cx).fmt(f)?;
- }
- Ok(())
+ bounds
+ .iter()
+ .filter(move |b| bounds_dup.insert(*b))
+ .map(|bound| bound.print(cx))
+ .joined(" + ", f)
})
}
@@ -190,12 +172,7 @@ impl clean::GenericParamDef {
if !outlives.is_empty() {
f.write_str(": ")?;
- for (i, lt) in outlives.iter().enumerate() {
- if i != 0 {
- f.write_str(" + ")?;
- }
- write!(f, "{}", lt.print())?;
- }
+ outlives.iter().map(|lt| lt.print()).joined(" + ", f)?;
}
Ok(())
@@ -245,10 +222,12 @@ impl clean::Generics {
return Ok(());
}
+ let real_params =
+ fmt::from_fn(|f| real_params.clone().map(|g| g.print(cx)).joined(", ", f));
if f.alternate() {
- write!(f, "<{:#}>", comma_sep(real_params.map(|g| g.print(cx)), true))
+ write!(f, "<{:#}>", real_params)
} else {
- write!(f, "<{}>", comma_sep(real_params.map(|g| g.print(cx)), true))
+ write!(f, "<{}>", real_params)
}
})
}
@@ -260,6 +239,42 @@ pub(crate) enum Ending {
NoNewline,
}
+fn print_where_predicate<'a, 'tcx: 'a>(
+ predicate: &'a clean::WherePredicate,
+ cx: &'a Context<'tcx>,
+) -> impl Display + 'a + Captures<'tcx> {
+ fmt::from_fn(move |f| {
+ match predicate {
+ clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
+ print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?;
+ ty.print(cx).fmt(f)?;
+ f.write_str(":")?;
+ if !bounds.is_empty() {
+ f.write_str(" ")?;
+ print_generic_bounds(bounds, cx).fmt(f)?;
+ }
+ Ok(())
+ }
+ clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
+ // We don't need to check `alternate` since we can be certain that neither
+ // the lifetime nor the bounds contain any characters which need escaping.
+ write!(f, "{}:", lifetime.print())?;
+ if !bounds.is_empty() {
+ write!(f, " {}", print_generic_bounds(bounds, cx))?;
+ }
+ Ok(())
+ }
+ clean::WherePredicate::EqPredicate { lhs, rhs } => {
+ if f.alternate() {
+ write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx))
+ } else {
+ write!(f, "{} == {}", lhs.print(cx), rhs.print(cx))
+ }
+ }
+ }
+ })
+}
+
/// * The Generics from which to emit a where-clause.
/// * The number of spaces to indent each line with.
/// * Whether the where-clause needs to add a comma and newline after the last bound.
@@ -270,55 +285,26 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
ending: Ending,
) -> impl Display + 'a + Captures<'tcx> {
fmt::from_fn(move |f| {
- let mut where_predicates = gens
- .where_predicates
- .iter()
- .map(|pred| {
- fmt::from_fn(move |f| {
- if f.alternate() {
- f.write_str(" ")?;
- } else {
- f.write_str("\n")?;
- }
+ if gens.where_predicates.is_empty() {
+ return Ok(());
+ }
- match pred {
- clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
- print_higher_ranked_params_with_space(bound_params, cx, "for")
- .fmt(f)?;
- ty.print(cx).fmt(f)?;
- f.write_str(":")?;
- if !bounds.is_empty() {
- f.write_str(" ")?;
- print_generic_bounds(bounds, cx).fmt(f)?;
- }
- Ok(())
- }
- clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
- // We don't need to check `alternate` since we can be certain that neither
- // the lifetime nor the bounds contain any characters which need escaping.
- write!(f, "{}:", lifetime.print())?;
- if !bounds.is_empty() {
- write!(f, " {}", print_generic_bounds(bounds, cx))?;
- }
- Ok(())
- }
- clean::WherePredicate::EqPredicate { lhs, rhs } => {
- if f.alternate() {
- write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx))
- } else {
- write!(f, "{} == {}", lhs.print(cx), rhs.print(cx))
- }
+ let where_preds = fmt::from_fn(|f| {
+ gens.where_predicates
+ .iter()
+ .map(|predicate| {
+ fmt::from_fn(|f| {
+ if f.alternate() {
+ f.write_str(" ")?;
+ } else {
+ f.write_str("\n")?;
}
- }
+ print_where_predicate(predicate, cx).fmt(f)
+ })
})
- })
- .peekable();
-
- if where_predicates.peek().is_none() {
- return Ok(());
- }
+ .joined(",", f)
+ });
- let where_preds = comma_sep(where_predicates, false);
let clause = if f.alternate() {
if ending == Ending::Newline {
format!(" where{where_preds},")
@@ -415,12 +401,7 @@ impl clean::GenericBound {
} else {
f.write_str("use<")?;
}
- for (i, arg) in args.iter().enumerate() {
- if i > 0 {
- write!(f, ", ")?;
- }
- arg.fmt(f)?;
- }
+ args.iter().joined(", ", f)?;
if f.alternate() { f.write_str(">") } else { f.write_str(">") }
}
})
@@ -438,29 +419,18 @@ impl clean::GenericArgs {
} else {
f.write_str("<")?;
}
- let mut comma = false;
- for arg in args.iter() {
- if comma {
- f.write_str(", ")?;
- }
- comma = true;
- if f.alternate() {
- write!(f, "{:#}", arg.print(cx))?;
- } else {
- write!(f, "{}", arg.print(cx))?;
- }
- }
- for constraint in constraints.iter() {
- if comma {
- f.write_str(", ")?;
- }
- comma = true;
- if f.alternate() {
- write!(f, "{:#}", constraint.print(cx))?;
- } else {
- write!(f, "{}", constraint.print(cx))?;
- }
- }
+
+ [Either::Left(args), Either::Right(constraints)]
+ .into_iter()
+ .flat_map(Either::factor_into_iter)
+ .map(|either| {
+ either.map_either(
+ |arg| arg.print(cx),
+ |constraint| constraint.print(cx),
+ )
+ })
+ .joined(", ", f)?;
+
if f.alternate() {
f.write_str(">")?;
} else {
@@ -470,14 +440,7 @@ impl clean::GenericArgs {
}
clean::GenericArgs::Parenthesized { inputs, output } => {
f.write_str("(")?;
- let mut comma = false;
- for ty in inputs.iter() {
- if comma {
- f.write_str(", ")?;
- }
- comma = true;
- ty.print(cx).fmt(f)?;
- }
+ inputs.iter().map(|ty| ty.print(cx)).joined(", ", f)?;
f.write_str(")")?;
if let Some(ref ty) = *output {
if f.alternate() {
@@ -524,6 +487,7 @@ pub(crate) enum HrefError {
// Panics if `syms` is empty.
pub(crate) fn join_with_double_colon(syms: &[Symbol]) -> String {
let mut s = String::with_capacity(estimate_item_path_byte_length(syms.len()));
+ // NOTE: using `Joined::joined` here causes a noticeable perf regression
s.push_str(syms[0].as_str());
for sym in &syms[1..] {
s.push_str("::");
@@ -572,20 +536,20 @@ fn generate_macro_def_id_path(
}
if let Some(last) = path.last_mut() {
- *last = Symbol::intern(&format!("macro.{}.html", last.as_str()));
+ *last = Symbol::intern(&format!("macro.{last}.html"));
}
let url = match cache.extern_locations[&def_id.krate] {
ExternalLocation::Remote(ref s) => {
// `ExternalLocation::Remote` always end with a `/`.
- format!("{s}{path}", path = path.iter().map(|p| p.as_str()).join("/"))
+ format!("{s}{path}", path = fmt::from_fn(|f| path.iter().joined("/", f)))
}
ExternalLocation::Local => {
// `root_path` always end with a `/`.
format!(
"{root_path}{path}",
root_path = root_path.unwrap_or(""),
- path = path.iter().map(|p| p.as_str()).join("/")
+ path = fmt::from_fn(|f| path.iter().joined("/", f))
)
}
ExternalLocation::Unknown => {
@@ -682,9 +646,8 @@ fn make_href(
url_parts.push("index.html");
}
_ => {
- let prefix = shortty.as_str();
let last = fqp.last().unwrap();
- url_parts.push_fmt(format_args!("{prefix}.{last}.html"));
+ url_parts.push_fmt(format_args!("{shortty}.{last}.html"));
}
}
Ok((url_parts.finish(), shortty, fqp.to_vec()))
@@ -950,12 +913,7 @@ fn tybounds<'a, 'tcx: 'a>(
cx: &'a Context<'tcx>,
) -> impl Display + 'a + Captures<'tcx> {
fmt::from_fn(move |f| {
- for (i, bound) in bounds.iter().enumerate() {
- if i > 0 {
- write!(f, " + ")?;
- }
- bound.print(cx).fmt(f)?;
- }
+ bounds.iter().map(|bound| bound.print(cx)).joined(" + ", f)?;
if let Some(lt) = lt {
// We don't need to check `alternate` since we can be certain that
// the lifetime doesn't contain any characters which need escaping.
@@ -974,7 +932,7 @@ fn print_higher_ranked_params_with_space<'a, 'tcx: 'a>(
if !params.is_empty() {
f.write_str(keyword)?;
f.write_str(if f.alternate() { "<" } else { "<" })?;
- comma_sep(params.iter().map(|lt| lt.print(cx)), true).fmt(f)?;
+ params.iter().map(|lt| lt.print(cx)).joined(", ", f)?;
f.write_str(if f.alternate() { "> " } else { "> " })?;
}
Ok(())
@@ -1025,9 +983,7 @@ fn fmt_type(
clean::Primitive(clean::PrimitiveType::Never) => {
primitive_link(f, PrimitiveType::Never, format_args!("!"), cx)
}
- clean::Primitive(prim) => {
- primitive_link(f, prim, format_args!("{}", prim.as_sym().as_str()), cx)
- }
+ clean::Primitive(prim) => primitive_link(f, prim, format_args!("{}", prim.as_sym()), cx),
clean::BareFunction(ref decl) => {
print_higher_ranked_params_with_space(&decl.generic_params, cx, "for").fmt(f)?;
decl.safety.print_with_space().fmt(f)?;
@@ -1067,18 +1023,16 @@ fn fmt_type(
primitive_link(
f,
PrimitiveType::Tuple,
- format_args!("({})", generic_names.iter().map(|s| s.as_str()).join(", ")),
+ format_args!(
+ "({})",
+ fmt::from_fn(|f| generic_names.iter().joined(", ", f))
+ ),
cx,
)
} else {
- write!(f, "(")?;
- for (i, item) in many.iter().enumerate() {
- if i != 0 {
- write!(f, ", ")?;
- }
- item.print(cx).fmt(f)?;
- }
- write!(f, ")")
+ f.write_str("(")?;
+ many.iter().map(|item| item.print(cx)).joined(", ", f)?;
+ f.write_str(")")
}
}
},
@@ -1407,16 +1361,17 @@ impl clean::Arguments {
cx: &'a Context<'tcx>,
) -> impl Display + 'a + Captures<'tcx> {
fmt::from_fn(move |f| {
- for (i, input) in self.values.iter().enumerate() {
- if !input.name.is_empty() {
- write!(f, "{}: ", input.name)?;
- }
- input.type_.print(cx).fmt(f)?;
- if i + 1 < self.values.len() {
- write!(f, ", ")?;
- }
- }
- Ok(())
+ self.values
+ .iter()
+ .map(|input| {
+ fmt::from_fn(|f| {
+ if !input.name.is_empty() {
+ write!(f, "{}: ", input.name)?;
+ }
+ input.type_.print(cx).fmt(f)
+ })
+ })
+ .joined(", ", f)
})
}
}
@@ -1725,12 +1680,7 @@ impl clean::ImportSource {
}
let name = self.path.last();
if let hir::def::Res::PrimTy(p) = self.path.res {
- primitive_link(
- f,
- PrimitiveType::from(p),
- format_args!("{}", name.as_str()),
- cx,
- )?;
+ primitive_link(f, PrimitiveType::from(p), format_args!("{name}"), cx)?;
} else {
f.write_str(name.as_str())?;
}
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 37fea09ace310..3f2e1f05c4a30 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1,5 +1,6 @@
use std::cmp::Ordering;
use std::fmt;
+use std::fmt::{Display, Write};
use itertools::Itertools;
use rinja::Template;
@@ -36,6 +37,7 @@ use crate::html::format::{
use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
use crate::html::render::{document_full, document_item_info};
use crate::html::url_parts_builder::UrlPartsBuilder;
+use crate::joined::Joined as _;
/// Generates a Rinja template struct for rendering items with common methods.
///
@@ -290,7 +292,7 @@ fn should_hide_fields(n_fields: usize) -> bool {
n_fields > 12
}
-fn toggle_open(mut w: impl fmt::Write, text: impl fmt::Display) {
+fn toggle_open(mut w: impl fmt::Write, text: impl Display) {
write!(
w,
"
\
@@ -305,7 +307,7 @@ fn toggle_close(mut w: impl fmt::Write) {
w.write_str(" ").unwrap();
}
-trait ItemTemplate<'a, 'cx: 'a>: rinja::Template + fmt::Display {
+trait ItemTemplate<'a, 'cx: 'a>: rinja::Template + Display {
fn item_and_cx(&self) -> (&'a clean::Item, &'a Context<'cx>);
}
@@ -519,13 +521,9 @@ fn extra_info_tags<'a, 'tcx: 'a>(
item: &'a clean::Item,
parent: &'a clean::Item,
import_def_id: Option,
-) -> impl fmt::Display + 'a + Captures<'tcx> {
+) -> impl Display + 'a + Captures<'tcx> {
fmt::from_fn(move |f| {
- fn tag_html<'a>(
- class: &'a str,
- title: &'a str,
- contents: &'a str,
- ) -> impl fmt::Display + 'a {
+ fn tag_html<'a>(class: &'a str, title: &'a str, contents: &'a str) -> impl Display + 'a {
fmt::from_fn(move |f| {
write!(
f,
@@ -1374,7 +1372,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
);
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
- fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ fn render_union<'b>(&'b self) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
fmt::from_fn(move |f| {
let v = render_union(self.it, Some(&self.s.generics), &self.s.fields, self.cx);
write!(f, "{v}")
@@ -1384,7 +1382,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
fn document_field<'b>(
&'b self,
field: &'a clean::Item,
- ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ ) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
fmt::from_fn(move |f| {
let v = document(self.cx, field, Some(self.it), HeadingOffset::H3);
write!(f, "{v}")
@@ -1398,7 +1396,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
fn print_ty<'b>(
&'b self,
ty: &'a clean::Type,
- ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ ) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
fmt::from_fn(move |f| {
let v = ty.print(self.cx);
write!(f, "{v}")
@@ -1425,7 +1423,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
fn print_tuple_struct_fields<'a, 'cx: 'a>(
cx: &'a Context<'cx>,
s: &'a [clean::Item],
-) -> impl fmt::Display + 'a + Captures<'cx> {
+) -> impl Display + 'a + Captures<'cx> {
fmt::from_fn(|f| {
if !s.is_empty()
&& s.iter().all(|field| {
@@ -1435,17 +1433,15 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>(
return f.write_str("");
}
- for (i, ty) in s.iter().enumerate() {
- if i > 0 {
- f.write_str(", ")?;
- }
- match ty.kind {
- clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_")?,
- clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx))?,
- _ => unreachable!(),
- }
- }
- Ok(())
+ s.iter()
+ .map(|ty| {
+ fmt::from_fn(|f| match ty.kind {
+ clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_"),
+ clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx)),
+ _ => unreachable!(),
+ })
+ })
+ .joined(", ", f)
})
}
@@ -2068,12 +2064,12 @@ fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>)
bounds.push_str(": ");
}
}
- for (i, p) in t_bounds.iter().enumerate() {
- if i > 0 {
- bounds.push_str(inter_str);
- }
- bounds.push_str(&p.print(cx).to_string());
- }
+ write!(
+ bounds,
+ "{}",
+ fmt::from_fn(|f| t_bounds.iter().map(|p| p.print(cx)).joined(inter_str, f))
+ )
+ .unwrap();
bounds
}
@@ -2150,7 +2146,7 @@ fn render_union<'a, 'cx: 'a>(
g: Option<&'a clean::Generics>,
fields: &'a [clean::Item],
cx: &'a Context<'cx>,
-) -> impl fmt::Display + 'a + Captures<'cx> {
+) -> impl Display + 'a + Captures<'cx> {
fmt::from_fn(move |mut f| {
write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?;
@@ -2330,7 +2326,7 @@ fn document_non_exhaustive_header(item: &clean::Item) -> &str {
if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" }
}
-fn document_non_exhaustive(item: &clean::Item) -> impl fmt::Display + '_ {
+fn document_non_exhaustive(item: &clean::Item) -> impl Display + '_ {
fmt::from_fn(|f| {
if item.is_non_exhaustive() {
write!(
diff --git a/src/librustdoc/joined.rs b/src/librustdoc/joined.rs
new file mode 100644
index 0000000000000..f369c6cf2371c
--- /dev/null
+++ b/src/librustdoc/joined.rs
@@ -0,0 +1,29 @@
+use std::fmt::{self, Display, Formatter};
+
+pub(crate) trait Joined: IntoIterator {
+ /// Takes an iterator over elements that implement [`Display`], and format them into `f`, separated by `sep`.
+ ///
+ /// This is similar to [`Itertools::format`](itertools::Itertools::format), but instead of returning an implementation of `Display`,
+ /// it formats directly into a [`Formatter`].
+ ///
+ /// The performance of `joined` is slightly better than `format`, since it doesn't need to use a `Cell` to keep track of whether [`fmt`](Display::fmt)
+ /// was already called (`joined`'s API doesn't allow it be called more than once).
+ fn joined(self, sep: &str, f: &mut Formatter<'_>) -> fmt::Result;
+}
+
+impl Joined for I
+where
+ I: IntoIterator- ,
+ T: Display,
+{
+ fn joined(self, sep: &str, f: &mut Formatter<'_>) -> fmt::Result {
+ let mut iter = self.into_iter();
+ let Some(first) = iter.next() else { return Ok(()) };
+ first.fmt(f)?;
+ while let Some(item) = iter.next() {
+ f.write_str(sep)?;
+ item.fmt(f)?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index af225c9d68dd9..98c4a60bd89a6 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -113,6 +113,7 @@ mod fold;
mod formats;
// used by the error-index generator, so it needs to be public
pub mod html;
+mod joined;
mod json;
pub(crate) mod lint;
mod markdown;