Fmt: Dynamic `fill` character

In macros like format! and println! it is possible to specify a custom fill width via an arg-within-an-arg:

println!("The weather is {:<width$} today.", "rainy", width = 10);

which prints

The weather is rainy      today.

We aren't, of course, limited to whitespace as a fill character:

println!("The weather is {:y<width$} today.", "rainy", width = 10);
The weather is rainyyyyyy today.

Nor, apparently, are we limited to ASCII:

println!("The weather is {:雨<width$} today.", "rainy", width = 10);
The weather is rainy雨雨雨雨雨 today.

Notice that in each case, we're using a char-literal to specify the fill. I would like to propose an extension to the formatting grammar to allow for dynamic specification of the fill character in a similar manner to the width.

Proposed Syntax

Two immediate possibilities come to mind:

Same Syntax

println!("The weather is {:fill$<width$} today.", "rainy", fill = 'y', width = 10);

Problem: But then what would {:foo$} specify, the fill char or the width? Note that even now, a fill character can be given without a width, but nothing is actually printed (e.g. {:y>}).

Different Syntax

Looking at the remaining symbols left on the top row of a keyboard, we have:

  • !: No, conflicts with the ! type.
  • @: Used in some languages to denote a variable.
  • #: No, conflicts with number formatting.
  • %: Reminiscent of C-formatting.
  • ^: No, used to specify alignment.
  • &: No, conflicts with references.
  • *: No, conflicts with math.

So how about @?

println!("The weather is {:fill@<width$} today.", "rainy", fill = 'y', width = 10);

This way, the fill and width could be hardcoded as usual (e.g. {:y<10}) or done dynamically, as shown above.

Thoughts? Please and thanks.

1 Like

Format grammar for reference.

Precision can be a parameter too; it has a $ suffix just like width. The leading . disambiguates.

// 3.14
println!("{:.prec$}", 3.14159, prec=2); 
// 0000003.14, 0-padded just to make this comment clear
println!("{:0width$.prec$}", 3.14159, width=10, prec=2);

An argument against $ is that width and precision are counts while fill is a character, and that justifies a different marker, but I personally find "I'm a parameter not a literal" to be the larger distinction.

If you specify fill, it must be followed by an align sigil (<, ^, >), so there is no ambiguity. In this case, foo is the width. {:foo$<} would be an example of parameterized fill.

4 Likes

Ah I missed that precision could be parameterized too! Well in that case I like your suggestion: $ seems appropriate for all three, with the align sigil providing disambiguity.

Could be the fill a parameter too?

The post clearly proposes that it can, yes.

The formatting syntax is very dense with lots of special characters having unobvious meanings, like a flavor of a regular expression. I'm not a fan of it. I can't use it without looking it up every time, and even then I'm not sure if I get it right.

Maybe all the configuration options could be taken out of the formatting string, where they can have a clearer self-describing syntax?

println!("{%described_elsewhere%}", CustomFormat { 
    width: 10, fill: "雨", align: "left", 
});
10 Likes

Would multiple custom formats be permitted? Something like/akin to:

let name = "Tom";
let age = 32;
let pet_kind_phrase = "a parrot";
let pet_name = "Polly";

println!("Hello, {%format_name%}! 
               Your age is: {%format_age%}. 
               Your favorite pet is {%format_pet_kind_phrase%} 
               that's named {%format_pet_name%}.", 
               name, 
               age,
               pet_kind_phrase, 
               pet_name, 
               format_name=CustomFormat{ ... }, 
               format_age=CustomFormat{...},
               ...
               format_pet_name=CustomFormat{...} );
2 Likes

I'm still in favour of a strict extension of the existing grammar, as opposed to an overhaul.

1 Like

Would the next steps for this be to open an official proposal somewhere on a Rust Github repository?

Maybe all the configuration options could be taken out of the formatting string, where they can have a clearer self-describing syntax?

Something like this would also make it massively easier to forward formatter arguments when implementing Debug or Display.

1 Like