SysTutorials

Printing to STDERR and STDOUT in OCaml

[md]
In OCaml, the standard library provides straightforward functions for writing to standard output and standard error.

## Printing to STDOUT

Use print_endline to write a string followed by a newline to standard output:

print_endline "hello world!"

This function is in the Stdlib module (formerly Pervasives in older OCaml versions) and automatically flushes the output buffer. The signature is:

val print_endline : string -> unit

For printing without a newline:

print_string "hello "

## Printing to STDERR

Use prerr_endline and prerr_string for standard error:

prerr_endline "error: something went wrong"
prerr_string "warning: "

The “prerr” prefix consistently indicates stderr output across the standard library.

## Printf-Style Formatted Output

OCaml’s Printf module provides type-safe formatted printing:

(* To stdout *)
Printf.printf "Name: %s, Age: %d, Score: %.2f\n" name age score

(* To stderr *)
Printf.eprintf "Error: %s (code %d)\n" msg code

Common format specifiers:

– %s — string
– %d — integer
– %f — float
– .2f — float with 2 decimal places
– %b — boolean (true/false)
– %c — character
– %a — user-defined formatter (advanced)

The compiler checks that the number and types of arguments match the format string — a mismatch is a compile-time error, not a runtime crash.

## Using Format for Pretty-Printing

For structured output with indentation and alignment, OCaml’s Format module is the standard approach:

let print_person name age =
  Format.printf "Person {@[@ name: %s;@ age: %d@]@,}@."
    name age

The Format module uses “pretty-print boxes” to manage layout:

– @[ — open a box
– @] — close a box
– @, — break hint (may become a newline)
– @; — break hint with custom spacing
– @. — flush and newline

## Redirecting Output Programmatically

You can redirect stdout or stderr to a file:

let with_output_to filename f =
  let oc = open_out filename in
  let old_stdout = Unix.dup Unix.stdout in
  Unix.dup2 (Unix.descr_of_out_channel oc) Unix.stdout;
  let result = f () in
  Unix.dup2 old_stdout Unix.stdout;
  close_out oc;
  result

(* Usage *)
let () =
  with_output_to "log.txt" (fun () ->
    print_endline "This goes to the file";
    42
  )

## Using the Logs Library

For production applications, the logs library provides structured logging with level filtering:

(* Install: opam install logs *)

(* In your application *)
Logs.info (fun m -> m "Server started on port %d" port);
Logs.warn (fun m -> m "Connection timeout after %.1f seconds" elapsed);
Logs.err (fun m -> m "Failed to open %s: %a" filename Fmt.exn exn);

(* Set log level *)
Logs.set_level (Some Logs.Warning);  (* Only warn and error *)

## Quick Reference

– print_endline — stdout with newline
– print_string — stdout without newline
– prerr_endline — stderr with newline
– prerr_string — stderr without newline
– Printf.printf — formatted stdout
– Printf.eprintf — formatted stderr
– Format.printf — pretty-printed stdout
– Logs.info/warn/err — structured logging

For most command-line tools, print_endline and prerr_endline cover the common cases. Reach for Printf when you need formatting, and Format when you need structured output.

## Flushing Output

OCaml buffers output for performance. If your program crashes or you need real-time output, flush explicitly:

print_string "Processing...";
flush stdout;  (* Force immediate output *)
(* Do long computation *)
print_endline "done"

The print_endline and prerr_endline functions flush automatically. But print_string and prerr_string do not — you must call flush stdout or flush stderr manually.

This matters for progress indicators:

let show_progress i total =
  let pct = 100 * i / total in
  Printf.printf "\rProgress: %d%%" pct;
  flush stdout

let () =
  for i = 1 to 1000 do
    show_progress i 1000;
    Unix.sleepf 0.01
  done;
  print_endline ""

## Capturing Output in Tests

When writing tests, you may want to capture stdout or stderr output:

let capture_stdout f =
  let buf = Buffer.create 256 in
  let old_stdout = stdout in
  let new_stdout = Stdlib.open_out_gen [Open_wronly; Open_creat] 0 "/dev/null" in
  (* Simplified: in practice use Unix.dup2 for proper capture *)
  let result = f () in
  result

(* Better approach: use the Camlunix or Core library's capture utilities *)

For testing, the simplest approach is often to use functions that return strings instead of printing, and only use printing in your main entry point:

(* Library function - returns string, easy to test *)
let format_result name score =
  Printf.sprintf "Result: %s scored %.1f" name score

(* CLI entry point - does the printing *)
let () =
  print_endline (format_result "Alice" 95.5)

This separation makes your code testable while still supporting formatted output.

← All posts