Printf is not a function

In many languages, formatted output looks like a normal function call. But compilers often treat those calls very differently. This post follows that strange pattern through Fortran, C, Pascal, OCaml, Python, and Rust.

It started with Fortran. There was no single function to do formatted output in Fortran, there was a special statement to do that. Here is how it looked like:

      WRITE OUTPUT TAPE 6, 601, IA, IB, IC, AREA
      FORMAT (4H A= ,I5,5H  B= ,I5,5H  C= ,I5,
      &       8H  AREA= ,F10.2, 13H SQUARE UNITS)
  
Needless to say, it was a distinguished language element, both the programmer and the compiler must have had a respect for it. And the statement itself looked quite involved, for a good reason -- see that 601 after the WRITE keyword, it stands for the line number of the relevant FORMAT form!

Going forward in history... Printf is so complicated it has been considered an attack surface. I think the reason is that the format string is not just data - it is a tiny language describing the shape and types of the following arguments.

An instructive limiting case is a dependently typed implementation of printf in Idris. There, printf can indeed be written as an ordinary function, but only because the type system is strong enough to compute the rest of the function's type from the format string. The implementation first parses the string into a Format value, then maps that value to a type: roughly, %d adds an Int ->, %s adds a String ->, and the end of the format finally returns String. The resulting signature is the revealing part: printf : (fmt : String) -> PrintfType (toFormat (unpack fmt)). So this is not a cheap trick that ordinary languages merely forgot to use. To make printf a regular function, the language must allow ordinary function types to depend on the value of an ordinary string argument.

References

  1. printf's history
  2. First language to have printf
  3. Mechanics of Algol's printf
  4. Confused user tries to understand C's printf
  5. Critique of printf (rust case)
  6. Attack on printf


return home