Implementing a Type-safe printf in Rust
willcrichton.net
willcrichton.net
IIRC he called it TypeList and used that to actually implement tuple as library type.
When C++11 variadic templates came out, a lot of C++ meta programmers breathed a huge sigh of relief of being able to abandon that technique. In addition, to being a pain to work with, because of the extensive use of recursion, it can take a large toll on compile times.
Variadic templates and being able manipulate tuples generically are probably the two biggest things I miss when programming Rust.
D's writefln() function uses variadic template specializations to achieve a similar result, meaning you can just use `%s` for the formats and writefln() knows how to print each type.
What if you, as library author (not compiler writer!), think of a much better formatting system? Or something that's maybe not better in general, but better for an unusual situation you're deploying in? Or you want to write something that's different to a formatting system but uses similar tricks? If you don't have language facilities to do this yourself then you're stuffed.
As well the original post showing it's possible in Rust, this is also possible in C++. For example, the C++ fmt [1] library allows compile-time time checking of the number of slots in format strings against the number of arguments, and even that the format specifications (.03d or whatever) match the passed in types. And it's not reliant on the compiler doing its own magic checking outside of the language.
There are languages (C++, Rust) where you can write your own, safe formatting library just using the language. It's a world away from printf.
Compared to that, parent comment says that in D the compiler has to have checks for formatting built in to it and compares it to printf. Yuk!
https://dlang.org/phobos/std_stdio.html#.writef
D can also call C's printf, and checking that is magical indeed. It exactly follows the C99 Standard for printf. A major feature of D is being able to call C functions directly, and printf is one of them. (D doesn't recognize printf specifically, it has to have a special attribute for it `pragma(printf)` which the compiler recognizes. This is so the checking can be applied to user-defined functions that use conforming printf-style format strings.)
https://dlang.org/spec/pragma.html#printf
And the code to do it:
https://github.com/dlang/dmd/blob/master/src/dmd/chkformat.d
I now see you were just making an unrelated point first. And having a specific compiler feature just for printf (and co.) is totally reasonable given how widespread it.
Your first link answers what would've been a follow up question: OK, writef() is a variadic template implemented with just normal language features, but does it do compile-time checking of the format? The docs says that it does:
> fmt: The format string. When passed as a compile-time argument, the string will be statically checked against the argument types passed.
I don't think D's writefln is getting any special treatment from the compiler. It seems to use D's compile-time features to perform the check.
https://github.com/dlang/phobos/blob/v2.093.0/std/stdio.d#L4...
D also has the capability of implementing string interpolation using mixins - though the resulting code isn't quite as neat.
It is of course very hard to have type safety when the format string is in a resource bundle
println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"
But you are right, you cannot dynamically change the format string.
If you need this facility, there is the runtime_fmt crate which will parse the format string at runtime instead of compile time. That of course means that it can fail at runtime if the format string does not match the arguments. println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"https://www.godbolt.org/z/xG15M5
Both clang and gcc support this, but unfortunately not the Microsoft C compiler.
PS: actually, it does, just not in the normal printf call: https://docs.microsoft.com/en-us/cpp/c-runtime-library/print...
(This is how the Scala ecosystem ended up working with records; technically the language doesn't have proper record types, but the whole ecosystem uses Shapeless to the extent that IDEs and other tooling are expected to understand the Shapeless macro, so de facto it does)
Because Idris supports dependent types it is prettier both in the implementation and usage.
This can make compilation slow but it can also make template writing very hard because a bug might only appear when a template is instantiated with certain types.
> This can make compilation slow but it can also make template writing very hard because a bug might only appear when a template is instantiated with certain types.
yup. templates are 'compiled' in 2 phases as follows:
- without instantiation at definition time, the code itself is checked for correctness, completely ignoring the template parameters.
- at instantiation time, the code is checked again to ensure that all parts depending on template parameters are valid.
the 2-phase translation breaks canonical notions about compile+link, where declarations of function is sufficient to compile its use...
`cout <<` is ugly and hard to use for anything but the simplest printing. Type safe printf seems like the obvious use case of variadic templates.
https://en.cppreference.com/w/cpp/utility/format and the actual proposal https://www.zverovich.net/2019/07/23/std-format-cpp20.html
- A reader can't really reason about what a macro might be doing, since they're immensely powerful.
- Tooling can't necessarily understand macros correctly, for the same reason. E.g. automated refactoring like extracting a repeated expression can't be relied on to work correctly.
Note that this is the kind of type machinery that Haskellers get excited about. If you're somewhat familiar about what type level programming in this area looks like there, check out Hlist[0], Vinyl[1][2], and if you're really interested, phantom types[3].
One of the best things about rust is that it manages to have both a near-research-grade type system, approaching C speed, and novel data safety features all rolled in one language. If those features weren't enough, it's got an extremely good module system with public package hosting, helpful compiler, relatively easy cross compilation and toolchain convenience other languages only dream of along with a welcoming community.
I'm not associated with the rust team/mozilla in any way, but there are very few reasons to not be excited about where rust will be in 10 years. Whether you're writing code to run in browsers (compiling your frontend code to wasm is a pretty intense step I'll admit -- I like my TS/JS just fine), web servers, or embedded code to run on your small ESP32 (or smaller), rust somehow fits.
[0]: https://hackage.haskell.org/package/HList
[1]: https://hackage.haskell.org/package/vinyl-0.13.0/docs/Data-V...
[2]: https://github.com/VinylRecords/Vinyl/blob/master/tests/Intr...
Is there still a Rust team at Mozilla?