std.format with wstring and dstring

H. S. Teoh hsteoh at qfbox.info
Mon Sep 8 15:39:23 UTC 2025


On Mon, Sep 08, 2025 at 01:09:02PM +1200, Richard (Rikki) Andrew Cattermole via Digitalmars-d-announce wrote:
> std.format is on my list of modules that I want replaced.
> 
> Its written with a lot of error conditions, rather than just adapting
> to the inputs. Hence lots of potential for exceptions being thrown
> that don't need to be.
> 
> Multiple multipliers in ``formattedWrite``: format string, output
> range.

IMO, a lot of template bloat could be removed by internally converting
output ranges to a delegate of static type that receives a const(char)[]
and writes to whatever output range was passed in from user code.  90%
of the std.format code does not actually care for the concrete type of
the output range; we do not need a copy of the entire formatting code
for every output range type passed in. Just erase the type at the entry
function and make most of the formatting code non-templated.


> What I'd like to do is to force IES for one or more values, if you
> want finer grained control you want do one value a time
> (``formatValue``).
> 
> ```d
> writeln(i"$i ${i:X}: $(j + 1)/${(j + 1):X} $1 ${0:X}", k, obj, "atend");
> ```

I'm still a fan of old-school format strings, I've to admit. Having to
manually type `formatValue(x), formatValue(y), ...` is just way too much
boilerplate.


> Require the use of a string builder, rather than any old output range.

Too much boilerplate to use a string builder.


> This is a required change for pretty printing. It requires the use of
> arbitrary inserts and removals that ranges can't do.

Format strings should be just strings.  It should not accept arbitrary
ranges (does it do that currently?).  Arguments may be ranges.


> Every template parameter like these that you have is a multiplier of
> instances, and that isn't good for compile times. Simplifying them
> down may seem like a pain, but it helps quite significantly. Given
> that there are some clear requirements and use cases we can in fact
> simplify it without hurting anyone enough to care.

See, the thing is that the current implementation of std.format goes
about things the wrong way.  It really should be just a thin wrapper
template, the sole purpose of which is to unpack the incoming arguments
and forward them to non-templated formatting functions. Or, at least,
formatting functions templated only on a *single* argument type (like
formatValue!int, formatValue!string, formatValue!float, ...), or perhaps
just overload on various basic types, maybe plus a couple of templates
for handling structs and classes, not on the entire `Args...` tuple of
types.  The latter causes combinatorial explosion of template instances,
which is both bloating and needless.  Only the top-level
std.format.format needs to be templated on `Args...`.  This should be
split up so that instead of O(n*m) template instantiations we have only
O(n) template instantiations (or preferably, O(1) template
instantiations if all the type-dependent stuff is handled at the top
level, and all lower-level functions are isolated formatting functions
that only do one job each).

Also, I dream of the day when we can pass compile-time format strings to
std.format and it will *only* instantiate the formatting functions that
you actually use. Float-formatting functions are particularly complex
and bloaty; why should your program pay for that extra baggage if you
never actually format a float?  The various formatting functions should
be pulled in only when you actually use them.  Just because you call
std.format with "%d" should not also pull in the whole shebang for
formatting floats, structs, classes, BigInts, and who knows what else.


T

-- 
Economics: (n.) The science of explaining why yesterday's predictions didn't come true today.


More information about the Digitalmars-d-announce mailing list