Intermediate

I/O Operations in Raku

Learn input and output in Raku - console output with say/put/printf, reading from stdin, and reading and writing files with slurp, spurt, and file handles

Input and output are where a program meets the outside world: the terminal, the keyboard, and the filesystem. Raku treats I/O as a first-class part of the language, giving you both convenient one-liners for common tasks and fully-featured file handles when you need control.

As a multi-paradigm language, Raku lets you write I/O the way that suits the job. You can read an entire file into a string with a single slurp, stream it line by line with a lazy sequence, or open an explicit handle and drive it method by method. Raku’s Unicode-first design means text I/O works correctly with international characters out of the box, and its exception system makes I/O errors easy to handle gracefully.

In this tutorial you’ll go beyond the say you met in Hello World. You’ll learn the family of output routines (say, put, print, printf, note), how to read from standard input, and how to read and write files using both the high-level slurp/spurt helpers and lower-level file handles with proper error handling.

Console Output

Raku offers several output routines, each suited to a different purpose. say is friendly and uses each value’s .gist, put uses the plain .Str, print adds nothing, printf gives you C-style formatting, and note writes to standard error.

Create a file named io_output.raku:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Console output methods in Raku
say "say: adds a newline and uses .gist";      # human-friendly form
put "put: adds a newline and uses .Str";       # plain string form
print "print: no newline added";
print "\n";

# printf for formatted output (C-style format strings)
printf "%-8s | %5.2f\n", "price", 3.5;
printf "%-8s | %5d\n", "count", 42;

# sprintf returns a formatted string instead of printing it
my $label = sprintf "[%03d]", 7;
say $label;

# say can print multiple values in one call
say "Sum: ", 2 + 3;

# note writes to STDERR, which is handy for diagnostics
note "note: this line goes to STDERR";

The printf directives work like other languages: %-8s left-justifies a string in an 8-character field, %5.2f renders a float in a 5-wide field with 2 decimals, and %03d zero-pads an integer to 3 digits.

Reading Input

Raku makes reading from the user straightforward. prompt prints a message and reads one line (with the trailing newline stripped), while get reads a single line from standard input. Because Raku numifies strings automatically in numeric context, you can do arithmetic on input without an explicit conversion.

Create a file named io_input.raku:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Reading input from the user in Raku

# prompt displays a message and reads one line (newline stripped)
my $name = prompt "Enter your name: ";
say "Hello, $name!";

# Strings numify automatically in numeric context
my $age = prompt "Enter your age: ";
say "Next year you will be { $age + 1 }.";

# get reads a single line from standard input
say "Type a word:";
my $word = get;
say "You typed: $word ({ $word.chars } characters)";

# To read every line from STDIN until EOF, iterate lines():
#   for lines() -> $line { say "Got: $line" }

Because this program waits for keyboard input, running it produces an interactive session like:

Enter your name: Camelia
Hello, Camelia!
Enter your age: 30
Next year you will be 31.
Type a word:
butterfly
You typed: butterfly (9 characters)

Reading and Writing Files

The quickest way to work with files in Raku is the spurt/slurp pair. spurt writes a whole string to a file (creating or overwriting it), and slurp reads an entire file back into a string. Add the :append adverb to add to a file instead of replacing it, and use .IO.lines to stream a file line by line.

Create a file named io_files.raku:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Writing and reading files in Raku
my $filename = "notes.txt";

# spurt writes an entire string to a file (creates or overwrites)
spurt $filename, "First line\nSecond line\nThird line\n";
say "Wrote to $filename";

# slurp reads the whole file back into a single string
my $contents = slurp $filename;
say "File contents:";
print $contents;

# Append more data with the :append adverb
spurt $filename, "Fourth line\n", :append;

# Read the file line by line (newlines are stripped)
say "Line by line:";
for $filename.IO.lines -> $line {
    say "$line";
}

# .lines in list context makes counting easy
my @all = $filename.IO.lines;
say "Total lines: ", @all.elems;

Notice $filename.IO — calling .IO on a string produces an IO::Path object, which is Raku’s gateway to path-based operations like .lines, .slurp, .e (exists), and .d (is a directory).

File Handles and Error Handling

When you need finer control — streaming output, staying open across many writes, or explicit mode selection — use open to get a file handle. Pair it with a CATCH block so that missing files or permission problems don’t crash your program.

Create a file named io_handles.raku:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Explicit file handles and I/O error handling

# open returns a file handle; :w opens for writing (truncates existing)
my $fh = open "log.txt", :w;
$fh.say("Log entry 1");
$fh.say("Log entry 2");
$fh.close;

# :r opens for reading (this is the default mode)
my $in = open "log.txt", :r;
for $in.lines -> $entry {
    say "Read: $entry";
}
$in.close;

# Gracefully handle errors when a file is missing
{
    slurp "does-not-exist.txt";
    CATCH {
        default {
            say "Could not read the file (it may not exist).";
        }
    }
}

say "Program continues normally.";

The CATCH block lives inside the block whose exceptions it handles. When slurp fails on a missing file it throws an exception; the default handler catches any exception type, prints a friendly message, and lets the program keep running.

Running with Docker

You can run every example above with the official Rakudo Star image — no local install required.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the Rakudo Star image
docker pull rakudo-star:alpine

# Run the console output example
docker run --rm -v $(pwd):/app -w /app rakudo-star:alpine raku io_output.raku

# Run the file read/write example
docker run --rm -v $(pwd):/app -w /app rakudo-star:alpine raku io_files.raku

# Run the file handle and error-handling example
docker run --rm -v $(pwd):/app -w /app rakudo-star:alpine raku io_handles.raku

# The input example needs an interactive terminal, so add -i
docker run --rm -i -v $(pwd):/app -w /app rakudo-star:alpine raku io_input.raku

Expected Output

Running io_output.raku prints to standard output (the note line is written to STDERR):

say: adds a newline and uses .gist
put: adds a newline and uses .Str
print: no newline added
price    |  3.50
count    |    42
[007]
Sum: 5

Running io_files.raku:

Wrote to notes.txt
File contents:
First line
Second line
Third line
Line by line:
  → First line
  → Second line
  → Third line
  → Fourth line
Total lines: 4

Running io_handles.raku:

Read: Log entry 1
Read: Log entry 2
Could not read the file (it may not exist).
Program continues normally.

Key Concepts

  • A family of output routinessay uses .gist for human-friendly output, put uses .Str, print adds nothing, printf gives C-style formatting, and note writes to STDERR for diagnostics.
  • slurp and spurt — read or write an entire file in one call; add the :append adverb to spurt to add to a file rather than overwrite it.
  • prompt and get — read a line of input with the trailing newline already stripped; strings numify automatically, so $age + 1 just works.
  • .IO produces an IO::Path — calling .IO on a string gives you path operations like .lines, .slurp, .e (exists), and .d (is a directory).
  • File handles via open — use :w, :r, or :a modes for explicit control, and always .close when finished writing.
  • Lazy line iteration.lines returns a lazy sequence, so you can process arbitrarily large files line by line without loading them entirely into memory.
  • CATCH for I/O errors — placing a CATCH block inside the enclosing block lets you handle missing files or permission errors gracefully instead of crashing.
  • Unicode by default — Raku’s text I/O handles international characters correctly, so writing and reading or 🦋 requires no special configuration.

Running Today

All examples can be run using Docker:

docker pull rakudo-star:alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining