Skip to content

std.io

std.io is console output over the print builtins, plus typed line input that reads a line through the read_line builtin and parses it. The module itself is small (four exported functions) because most I/O in dusk lives in builtins that are available everywhere without an import. This page documents both: the std.io functions and the printing, stdin, and file builtins they sit on.

@import std.io

Imported names are flat: after @import std.io you call print_int and print_line with no prefix. You can also qualify a call through its module path, so std.io.print_line("hi") reaches the same function. See stdlib overview for how imports work in general.

print, println, and printerr are builtins, available everywhere without an import and gated behind no paradigm.

BuiltinSignatureDescription
printprint(...) -> voidPrint to stdout, no newline.
printlnprintln(...) -> voidPrint to stdout, appending a newline.
printerrprinterr(...) -> voidprintln to stderr.

Each handles a string, an int, a float, a bool, or a char. Build a line piece by piece with print, then close it with println.

print("score: ")
print(42)
println("") // ends the line

A struct can be printed if it implements the Display interface. Passing a struct with no Display impl to a print builtin is a compile error, as is printing an enum, a slice, a tuple, or a pointer. Print never emits silence for a value it cannot render. See builtins for the Display interface.

printerr writes a line to stderr, flushing stdout first so program output precedes the message even when the program aborts right after. It also accepts an error value directly, which is the usual way to report a failure before returning:

n, e := read_int()
if e.exists() {
printerr(e)
return 1
}

With a value argument, the first argument to print, println, or printerr is a format string whose {} holes the rest fill in order. Write {{ or }} for a literal brace. The format string is a literal expanded at compile time, so each hole prints its value by type with no runtime format parser and no allocation, and a hole count that does not match the arguments is a compile error.

io_printing.dusk
@paradigm procedural
func main() -> int32 {
print("score: ")
print(42)
println("")
name: string = "Ada"
age: int64 = 36
println("{} is {}", name, age)
println("{{braces}} and a hole {}", 7)
return 0
}

These are the four exported functions in std.io, reachable after @import std.io.

func print_int(n: int64) -> void

Prints an integer and a newline. A thin wrapper over println(n).

func print_line(s: string) -> void

Prints a string and a newline. A thin wrapper over println(s).

func read_int() -> (int64, error)

Reads one line from stdin through the read_line builtin and parses it as a base 10 integer with parse_int from std.string. The error exists at end of input or when the line is not an integer, and the value beside a real error is 0.

func read_float() -> (float64, error)

Reads one line from stdin and parses it as a float with parse_float from std.string. The error exists at end of input or when the line is not a number, and the value beside a real error is 0.0.

Both readers return a (value, error) pair, so the must-handle rule applies: the caller resolves the error through exists, check, or ignore. See error handling.

io_readnum.dusk
@paradigm procedural
@import std.io
func main() -> int32 {
print_line("enter an int:")
n, e := read_int()
if e.exists() {
print_line("not an int")
return 0
}
print_int(n * 2)
print_line("enter a float:")
f, fe := read_float()
if fe.exists() {
print_line("not a float")
return 0
}
println(f + 1.0)
return 0
}

read_line and read_all are builtins that read from stdin, available without an import.

BuiltinSignatureDescription
read_lineread_line() -> (string, error)Read one line from stdin, the error marking end of input.
read_allread_all() -> (string, error)Read all of stdin into one string.

read_line reads one line, read_all reads the whole stream. For read_line the error exists at end of input, so a read loop stops when it fires, while read_all errors only on an allocation failure, since the whole of an empty stream is the empty string. A line keeps no trailing newline, and an empty line is the empty string, which is distinct from end of input. Both read from the terminal when stdin is not redirected, and from a pipe or a file when it is.

dusk has no break, so read until end of input with a done flag. The loop needs mut and while, which require @paradigm procedural. See the paradigm system.

io_echo.dusk
@paradigm procedural
@import std.io
func main() -> int32 {
print_line("what is your name?")
name, err := read_line()
if err.exists() {
print_line("no input")
return 0
}
println("hello {}", name)
print_line("enter lines, end with ctrl-d:")
mut count: int64 = 0
mut done: bool = false
while !done {
line, e := read_line()
if e.exists() {
done = true
} else {
print_line(line)
count = count + 1
}
}
print_int(count)
return 0
}

read_file and write_file are builtins, so they are available everywhere without an import, the same way print is.

BuiltinSignatureDescription
read_fileread_file(path: string) -> (string, error)Read the whole file into a heap string.
write_filewrite_file(path: string, contents: string) -> errorWrite the string to the file, truncating it.

read_file returns a (string, error) pair and write_file returns an error, so the must-handle rule applies and a caller resolves the failure through exists, check, or ignore. A failed read hands back the empty string and an error that exists.

The string read_file returns lives on the heap and is owned by the caller, so free it with free once you are done with it. It frees through the generational heap like any other allocation. See memory.

io_fileio.dusk
@paradigm procedural
@import std.io
@import std.string
func main() -> int32 {
werr := write_file("/tmp/dusk_io_example.tmp", "persisted")
werr.ignore()
s, rerr := read_file("/tmp/dusk_io_example.tmp")
rerr.ignore()
print_line(s)
print_int(str_len(s))
free(s)
return 0
}
  • std.string: parse_int, parse_int_radix, and parse_float, the parsers behind read_int and read_float.
  • Builtins: the full builtin table, including the Display interface for printing structs.
  • Error handling: the must-handle rule and the exists, check, and ignore methods.