← 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: GujiGoOCamlHaskellPerlRakuRustPython
Guji
sub main() {
    # Real stdin: stdin is a Handle; .lines() yields a
    # Chan[Str] of chomped lines, drained by `for ... in`.
    $lines = stdin.lines()
    mut $n = 1
    for $line in $lines {
        print("$n: $line")
        $n = $n + 1
    }
}

Guji v0.1-alpha is a compiled, statically-typed language with real platform IO (spec §15.4), so this reads actual stdin - the same cat -n task as the others. stdin is a Handle (no parentheses); .slurp() gives the whole input, while .lines() returns a Chan[Str] of chomped lines. Binding it to a local ($lines) lets for $line in $lines drain the channel until it closes; that local binding is also what the native AOT backend needs (guji build). The counter is an explicit mut $n (§4) since Guji is immutable-by-default with no built-in enumerate, and "$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.

Haskell
main :: IO ()
main = do
  contents <- getContents              -- lazy stream of all stdin
  let numbered = zip [1 :: Int ..] (lines contents)
  mapM_ (\(n, line) -> putStrLn (show n ++ ": " ++ line)) numbered

getContents reads stdin lazily, so pairing it with lines and zip [1..] streams input rather than slurping it - the list is consumed as it is produced. zip [1..] against the lines is Haskell's natural "enumerate", supplying 1-based numbers with no mutable counter. mapM_ runs the putStrLn side effect for each numbered line, and lines has already stripped the trailing newlines.

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.