Skip to content

Conversation

@dianne
Copy link
Contributor

@dianne dianne commented Feb 11, 2026

Proof-of-concept PR to help resolve the lets-see-the-optimization concern on #149926. I tried to aim for something as decoupled as possible from the rest of the format_args! logic, rather than a clever or efficient implementation. This doesn't do anything to optimize captured width/precision arguments. I've left more details inline.

dianne and others added 2 commits February 11, 2026 03:08
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Feb 11, 2026
fmt = flatten_format_args(fmt);
fmt = self.inline_literals(fmt);
}
fmt = self.dedup_captured_places(fmt);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The de-duplication happens here as a separate pass, heavily based on the unstable pass to flatten nested format_args! and inline literals.

Comment on lines +192 to +212
// Remove the arguments that were de-duplicated.
if deduped_anything {
let fmt = fmt.to_mut();

// Drop all the arguments that are marked for removal.
let mut remove_it = remove.iter();
fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&true));

// Calculate the mapping of old to new indexes for the remaining arguments.
let index_map: Vec<usize> = remove
.into_iter()
.scan(0, |i, remove| {
let mapped = *i;
*i += !remove as usize;
Some(mapped)
})
.collect();

// Correct the indexes that refer to arguments that have shifted position.
for_all_argument_indexes(&mut fmt.template, |index| *index = index_map[*index]);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was all copy-pasted from inline_literals. It should either be factored out or made more specialized. E.g. here it doesn't have to iterate through all arguments, since captured arguments should always be last, I think? Probably there's plenty of other things it could do to be more clever.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dianne Captured args aren't always last:

let x = 42;
println!("{x} {} {x}", "==");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By last, I mean in fmt.arguments's Vec. To my understanding, positional arguments come first, then named arguments, and then captured arguments are last. Since only captured arguments would be removed by the de-duplication pass, we could do something like (ignoring privacy, readability, and other practical concerns)

fmt
    .arguments
    .all_args_mut()
    .extract_if(fmt.arguments.num_explicit_args.., |_| /* ... */)
    .for_each(drop)

It feels excessive, and it would probably need a helper on rustc_ast::FormatArguments to avoid exposing to the rest of rustc that arguments are sorted in that way, but in theory it would avoid some iteration.

Comment on lines +56 to +63
// We don't de-duplicate widths or precisions since de-duplication can be observed.
let _ =
{
super let args = (&x, &x, &x);
super let args =
[format_argument::new_display(args.0),
format_argument::from_usize(args.1),
format_argument::from_usize(args.2)];
Copy link
Contributor Author

@dianne dianne Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could get this down to only capturing &x once, but it'd still need two separate from_usize calls to always be the same as providing the width two separate times. This is similar to my understanding of why {a.b} is tricky to fully de-duplicate: if x has an impure/pathological impl Deref<Target = usize>, the deref coercion of &x to a &usize could return different values each time (or modify global state, etc.).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't try to do that optimization in this PR since I think de-duplicating the from_usize calls would mean adding hacks to more central parts of the format_args! expansion.

@joshtriplett joshtriplett added the I-libs-api-nominated Nominated for discussion during a libs-api team meeting. label Feb 11, 2026
Entry::Vacant(vacant_entry) => {
// This is the first time we've seen a captured identifier. If it's a local
// or static, note the argument index so other occurrences can be deduped.
if let Some(partial_res) = self.resolver.partial_res_map.get(&arg.expr.id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be the first use of self.resolver in format_args. I understand that's the entire point of this PR, but that's exactly the thing that I think is problematic. This is what would make format_args too magical for me.

Copy link
Member

@joshtriplett joshtriplett Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a separate optimization pass, which is what makes it seem reasonable to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

I-libs-api-nominated Nominated for discussion during a libs-api team meeting. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants