io/print_str appends an implicit newline (de facto println) #29

Closed
opened 2026-05-21 11:40:38 +02:00 by Brummel · 0 comments
Owner

The effect operation io/print_str is named print_str but the runtime codegen lowers it to a C @puts call (crates/ailang-codegen/src/lib.rs around the io/print_str branch). puts writes the string plus a trailing newline — so io/print_str is in fact println_str.

This is undocumented in the design/ ledger (effects.md, prelude-classes.md, str-abi.md mention the op but not the newline semantics) and surprises LLM authors: in the cross-model-authoring naming-A/B run, every Qwen3-Coder output that wrote explicit "\\n" after the int_to_str produced doubled newlines and failed the stdout match for t3_main_prints across all three cohorts.

Two coherent fixes (pick one — separate spec discussion):

  1. Drop the auto-newline. Switch codegen to fputs(s, stdout) so io/print_str literally prints exactly the bytes of s. The name then matches the behaviour. Authors who want a newline write "\\n" themselves.
  2. Rename the op to io/println_str (or io/print_line) and document the auto-newline. Either way: stop calling it print_str while it secretly newlines.

Option 1 is semantically cleaner and matches the name; option 2 is the smaller change. The design/contracts/effects.md spec needs to spell out whichever choice is made.

The effect operation `io/print_str` is named `print_str` but the runtime codegen lowers it to a C `@puts` call (crates/ailang-codegen/src/lib.rs around the `io/print_str` branch). `puts` writes the string *plus a trailing newline* — so `io/print_str` is in fact `println_str`. This is undocumented in the design/ ledger (effects.md, prelude-classes.md, str-abi.md mention the op but not the newline semantics) and surprises LLM authors: in the cross-model-authoring naming-A/B run, every Qwen3-Coder output that wrote explicit `"\\n"` after the int_to_str produced doubled newlines and failed the stdout match for t3_main_prints across all three cohorts. Two coherent fixes (pick one — separate spec discussion): 1. Drop the auto-newline. Switch codegen to `fputs(s, stdout)` so `io/print_str` literally prints exactly the bytes of `s`. The name then matches the behaviour. Authors who want a newline write `"\\n"` themselves. 2. Rename the op to `io/println_str` (or `io/print_line`) and document the auto-newline. Either way: stop calling it `print_str` while it secretly newlines. Option 1 is semantically cleaner and matches the name; option 2 is the smaller change. The design/contracts/effects.md spec needs to spell out whichever choice is made.
Brummel added the bug label 2026-05-21 11:40:38 +02:00
Sign in to join this conversation.