← Code Compare

Reading Input: lines, files & basic I/O

The same little cat -n: read input line by line and print each line prefixed with its 1-based line number. Watch how each language gets a stream of lines and how it counts: a buffered scanner with a manual counter (Go), a buffered reader whose .lines() iterator chains straight into enumerate (Rust, Python), the implicit line-number variable $. (Perl), built-in .lines with .kv (Raku), or a recursive read-until-End_of_file loop (OCaml). guji v0 is the outlier: its only I/O primitive is print, so input is modelled as an in-program value split with .lines().

Show: gujiGoOCamlPerlRakuRustPython
guji
sub main(): Int {
    # v0's only I/O primitive is print, so model "input"
    # as a value, then read it line by line with .lines().
    $input = "alpha\nbeta\ngamma"
    mut $n = 1
    for $line in $input.lines() {
        print("$n: $line")
        $n = $n + 1
    }
    0
}

Per the spec (§15.4) print is the only v0 I/O primitive — file, stdin, and stderr APIs are deferred — so genuine reading isn't expressible yet; the input is a Str value split by the prelude's lines() (§15.3). Iteration that has a side effect uses for (§6.3); the counter is an explicit mut $i (§4), since guji is immutable-by-default and has no built-in enumerate. The "$n: $line" interpolation (§12) builds each output line.

Go
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	n := 0
	for scanner.Scan() {
		n++
		fmt.Printf("%d: %s\n", n, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "read error:", err)
	}
}

bufio.NewScanner(os.Stdin) is the standard line reader: Scan() advances to the next token (lines by default) and Text() returns it with the newline stripped. The counter n is incremented by hand — Go has no enumerate. Idiomatic Go checks scanner.Err() after the loop, since Scan() returning false means either EOF or an error, and the two are distinguished only there.

OCaml
let () =
  let rec loop n =
    match input_line stdin with
    | line -> Printf.printf "%d: %s\n" n line; loop (n + 1)
    | exception End_of_file -> ()
  in
  loop 1

OCaml's stdlib reads a line with input_line, which raises End_of_file at the end rather than returning an option, so the loop is a tail-recursive loop that pattern-matches the call's result — the exception End_of_file arm is OCaml's syntax for catching that exception inline. The line counter is just the recursion argument n, threaded immutably. input_line already strips the trailing newline.

Perl
use strict;
use warnings;

while (my $line = <STDIN>) {
    chomp $line;
    print "$.: $line\n";
}

The diamond/angle read <STDIN> yields one line per iteration (including its newline), and the while (my $line = ...) idiom stops cleanly at EOF — even on a final line like 0 — because the assignment is specially tested for definedness. chomp removes the trailing newline, and $. is the magic input line-number variable Perl maintains automatically, so no manual counter is needed.

Raku
for $*IN.lines.kv -> $i, $line {
    say "{ $i + 1 }: $line";
}

$*IN is the standard-input handle and .lines is a lazy sequence of newline-stripped lines, so this streams rather than slurping. .kv pairs each element with its index, bound by the pointy block -> $i, $line; $i is 0-based, so the output adds one. say appends the newline, and { ... } interpolates the expression inside the double-quoted string.

Rust
use std::io::{self, BufRead};

fn main() {
    let stdin = io::stdin();
    for (i, line) in stdin.lock().lines().enumerate() {
        let line = line.expect("read error");
        println!("{}: {}", i + 1, line);
    }
}

stdin.lock() hands back a buffered StdinLock (it implements BufRead), whose .lines() is an iterator of Result<String, io::Error> with newlines already trimmed. Chaining .enumerate() supplies the 0-based index i, so the program prints i + 1. Each line is a Result, made explicit here by .expect, reflecting that any read can fail — the error is part of the type, not a silent EOF.

Python
import sys

for n, line in enumerate(sys.stdin, start=1):
    print(f"{n}: {line.rstrip(chr(10))}")

Iterating sys.stdin directly yields lines lazily, one at a time, so the whole input is never held in memory. enumerate(..., start=1) supplies the 1-based line number without a manual counter — the most Pythonic way to count while iterating. Unlike most languages here, the lines keep their trailing newline, so rstrip removes it before the f-string reformats each line.