File I/O & Paths
Sandbox capability
file/* functions require the FS_READ capability (for reads, listings, predicates) or FS_WRITE capability (for writes, deletes, renames, mkdir). They run unrestricted under sema by default, but are gated in sandboxed environments (e.g., the WASM playground). A sandboxed script that attempts to use them without the capability will receive an error.
Console I/O
display
Print a value without a trailing newline.
(display "no newline")
(display 42)println
Print a value followed by a newline.
(println "with newline")
(println 42)print
Write values in read-syntax form (strings are quoted) like Scheme's write. No trailing newline. Use display for human-readable output without quotes.
(print "hello") ;; outputs: "hello"
(display "hello") ;; outputs: helloio/print-error
Print to stderr without a trailing newline.
(io/print-error "warning: something happened")io/println-error
Print to stderr with a trailing newline.
(io/println-error "error: file not found")newline
Print a newline character.
(newline)io/read-line
Read a line of input from stdin (trailing \n / \r\n stripped).
(define name (io/read-line))Returns nil when stdin is closed (Ctrl-D in cooked mode, end of a piped file). Use this to distinguish "user pressed Enter on an empty line" (returns "") from "stdin is exhausted" (returns nil).
(let loop ()
(let ((line (io/read-line)))
(cond
((nil? line) (println "(eof)"))
((= line "") (loop)) ; blank line, keep reading
(else (println "got: " line) (loop)))))Breaking change in 1.14.0
Previously io/read-line returned "" on both EOF and empty input, making them indistinguishable. It now returns nil on EOF. If you don't want to refactor for this, use io/eof? after the call instead.
io/read-stdin
Read all of stdin as a string (until EOF).
(define input (io/read-stdin))io/eof?
Return #t after any stdin read (io/read-line, io/read-stdin, io/read-key) has signalled EOF. Non-breaking alternative to checking io/read-line for nil.
(define line (io/read-line))
(when (io/eof?)
(println "stdin closed"))io/flush
Flush stdout. Useful when writing a prompt without a trailing newline before reading input.
(display "name> ")
(io/flush)
(define name (io/read-line))File Operations
file/read
Read the entire contents of a file as a string.
(file/read "data.txt") ; => "file contents..."file/write
Write a string to a file, overwriting any existing content.
(file/write "out.txt" "content")file/append
Append a string to a file.
(file/append "log.txt" "new line\n")file/read-lines
Read a file as a list of lines. Handles both \n and \r\n line endings. An empty file returns an empty list.
(file/read-lines "data.txt") ; => ("line 1" "line 2" "line 3")
(file/read-lines "empty.txt") ; => ()file/write-lines
Write a list of strings to a file, one per line.
(file/write-lines "out.txt" '("a" "b" "c"))file/for-each-line
Iterate over lines of a file, calling a function on each line. Memory-efficient for large files.
(file/for-each-line "data.txt"
(fn (line) (println line)))file/fold-lines
Fold over lines of a file with an accumulator. Uses a 256KB buffer for high throughput on large files.
(file/fold-lines "data.csv"
(fn (acc line) (+ acc 1))
0)
; => number of linesfile/delete
Delete a file.
(file/delete "tmp.txt")file/rename
Rename or move a file.
(file/rename "old.txt" "new.txt")file/copy
Copy a file.
(file/copy "src.txt" "dst.txt")Binary File I/O
file/read-bytes
Read a file as a bytevector (binary data).
(file/read-bytes "image.png") ; => #u8(137 80 78 71 ...)file/write-bytes
Write a bytevector to a file.
(file/write-bytes "output.bin" my-bytes)File Predicates
file/exists?
Test if a file or directory exists.
(file/exists? "data.txt") ; => #t or #ffile/is-file?
Test if a path is a regular file.
(file/is-file? "data.txt") ; => #tfile/is-directory?
Test if a path is a directory.
(file/is-directory? "src/") ; => #tfile/is-symlink?
Test if a path is a symbolic link.
(file/is-symlink? "link") ; => #t or #fDirectory Operations
file/list
List entries in a directory.
(file/list "src/") ; => ("main.rs" "lib.rs" ...)file/mkdir
Create a directory.
(file/mkdir "new-dir")file/glob
Find files matching a glob pattern.
(file/glob "src/**/*.rs") ; => ("src/main.rs" "src/lib.rs" ...)
(file/glob "*.txt") ; => ("readme.txt" "notes.txt")file/info
Get file metadata. Returns a map with :size, :modified, and other keys.
(file/info "data.txt") ; => {:size 1234 :modified 1707955200 ...}Path Manipulation
path/join
Join path components.
(path/join "src" "main.rs") ; => "src/main.rs"
(path/join "a" "b" "c.txt") ; => "a/b/c.txt"path/dir
Return the directory portion of a path. Returns "" when the path has no parent component.
(path/dir "/a/b/c.txt") ;; => "/a/b"
(path/dir "foo") ;; => ""path/dirname is a legacy alias for path/dir — same implementation, same return value.
path/filename
Return the filename portion of a path. Returns "" when there is no filename component (e.g. for "").
(path/filename "/a/b/c.txt") ;; => "c.txt"
(path/filename "plain.rs") ;; => "plain.rs"path/basename is a legacy alias for path/filename — same implementation, same return value.
path/extension
Return the file extension (without the dot). Returns "" when the path has no extension.
(path/extension "file.rs") ;; => "rs"
(path/extension "file.tar.gz") ;; => "gz"
(path/extension "Makefile") ;; => ""
(path/extension ".hidden") ;; => ""path/ext is a legacy alias for path/extension — same implementation, same return value.
Behavior change
Previous versions registered path/dirname, path/basename, and path/extension as independent functions that returned nil on the no-parent / no-filename / no-extension case. As of the current release, all six names share one implementation per concept and consistently return "" (matching path/dir, path/filename, path/ext).
path/absolute
Return the absolute path.
(path/absolute ".") ; => "/full/path/to/current/dir"path/stem
Return the filename without extension.
(path/stem "file.rs") ; => "file"
(path/stem "archive.tar.gz") ; => "archive.tar"path/absolute?
Test if a path is absolute.
(path/absolute? "/usr/bin") ; => #t
(path/absolute? "relative") ; => #f