Feedback Wanted on Homegrown @nogc WriteLn Alternative

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Thu Oct 2 16:30:33 PDT 2014


On Sat, Sep 27, 2014 at 01:06:24PM +0200, Andrej Mitrovic via Digitalmars-d wrote:
> On 9/27/14, H. S. Teoh via Digitalmars-d <digitalmars-d at puremagic.com> wrote:
> > 	writefln!"...format string here"(... /* arguments here */);
> 
> Mmm, I like this. It would be one of those killer little features to
> show in a talk. Slide 1:
> 
> // oops, forgot an %s
> writefln("%s %s", 1, 2, 3);
> 
> Slide 2:
> 
> // Programmer error caught at compile-time!
> writefln!("%s %s")(1, 2, 3);

Alright, today I drafted up the following proof of concept:

	private size_t numSpecs(string fmt)
	{
	    size_t count = 0;
	
	    for (size_t i=0; i < fmt.length; i++)
	    {
	        if (fmt[i] == '%')
	        {
	            i++;
	            if (i < fmt.length && fmt[i] == '%')
	                continue;
	            count++;
	        }
	    }
	    return count;
	}
	
	void writef(string fmt="", Args...)(Args args)
	{
	    import std.format : formattedWrite;
	    import std.stdio : stdout;
	
	    static if (fmt == "")
	    {
	        // fmt == "" means we're using a runtime-specified format string.
	        static if (args.length >= 1)
	        {
	            static if (is(typeof(args[0]) : string))
	                stdout.lockingTextWriter().formattedWrite(args[0], args[1..$]);
	            else
	                static assert(0, "Expecting format string as first argument");
	        }
	    }
	    else
	    {
	        // Compile-time specified format string: we can run sanity checks on
	        // it at compile-time.
	        enum count = numSpecs(fmt);
	        static assert(args.length == count,
	                      "Format string requires " ~ count.stringof ~
	                      " arguments but " ~ args.length.stringof ~
	                      " are supplied");
	
	        // For now, we just forward it to the runtime format implementation.
	        // The eventual goal is to only depend on formatting functions that
	        // the format string actually uses, so that we can be @safe, nothrow,
	        // @nogc, pure, etc., as long as the dependent parts don't break any of
	        // those attributes.
	        stdout.lockingTextWriter().formattedWrite(fmt, args);
	    }
	}
	
	void writefln(string fmt="", Args...)(Args args)
	{
	    writef!(fmt)(args);
	    writef!"\n";
	}
	
	void main()
	{
	    //writefln!"Number: %d Tag: %s"(123, "mytag", 1);
	    writefln!"Number: %d Tag: %s"(123, "mytag");
	}

If you uncomment the first line in main(), you'll get a nice
compile-time error telling you that the number of format specifiers and
the number of actual arguments passed don't match.

This is just a proof-of-concept, mind you; it doesn't actually parse the
format string correctly (it's just counting the number of unescaped %'s,
but that doesn't necessarily correspond with the number of arguments
needed, e.g., if you use "%*s" or "%(...%)").

But it *does* prove that it's possible to achieve compatibility with the
current way of invoking writefln, that is, if you write:

	writefln("format %s string", "abc");

it will actually compile as before, and work as expected.

So this new syntax can be implemented alongside the existing syntax and
people can gradually migrate over from purely-runtime format strings to
compile-time, statically-checked format strings.


T

-- 
"Uhh, I'm still not here." -- KD, while "away" on ICQ.


More information about the Digitalmars-d mailing list