Another way to do it would be an alternative to std.Io.Writer.print that still has comptime format string, and still takes a tuple, but is inline and lowers to a call to a non-generic, type-erased function.
Since this has identical function signature, it might even make sense to offer this as an application configuration option in the standard library
@kristoff I don’t mean runtime strings, I mean a type-erased interface over a (comptime string, args) pair. The same way Thread.Pool makes a type-erased interface over a pair of (comptime function, args).
@andrewrk I’m not convinced that would solve it. I probably didn’t explain it well, but in the snippet I was trying to show that doThing shouldn’t have any knowledge of the format args.
If I passed the differently-typed arg tuples all the way down to finalThing and print (even with an alternative Writer.print), every function in the call stack between main and finalThing would have to be generic. I want to avoid that.
I had actually started something like that but it was on 0.13 and a lot of stuff broke in the meantime, including the comptime logic.
My idea was to have single fn format(tape: []const Fmt , args: []const u8) the caller would pass a pointer to the args tuple, and the function would interpret the bytes based on the “tape” metadata. The “tape” was created at comptime.
Did you have another idea in mind?
At the time I also estimated that 15% of Zig compiler binary was generated fmt functions.
I think runtime formatting is definitely something worth investigating but not what the original post is about as far as I can tell.
Perhaps something like this?
Oh, I just realised that that will definitely fail when closing over other values, that’d need to be considered and put into DelayedFormat as well, should be pretty simple.
Edit: “Should be pretty simple,” I say. Right…
Main challenge there is coming up with a way to not include, for example, f128 printing code if such thing is not needed.
I can think of a way to do it, but unsure if it’s in poor taste. One idea is to call through a weak extern function that does the rare, expensive printing. This is initialized to null but in the logic that processes the comptime string, if such printing is encountered, it would reference an exported matching function definition.
I guess it might make sense to specialize this to popular types like ints, but there’s going to be some dynamic dispatch somewhere to handle {f}, and f128 could go through that machinery?
I was in a need for runtime formatting a few times already (and I did what Andrew was suggesting), but I never (so far) needed f128 printing, not even in comptime. Am I missing some common use-case or why is it a thing? IIRC Rust/Go didn’t have formatting for f128 either (maybe they still don’t, I just don’t care anymore).
I think he meant how to not include f128 formatting code in program that does not use it. i.e if a program only use {s}, there should not be any code included in for printing ints or floats… So further code size reduction.
Yes, I think this would be best of the both worlds. The non-generic runtime version of printing could be used for runtime translations (multi-lingual) stuff as well assuming it still has safety checks between format string and input.