Streams
Streams are first-class byte-oriented I/O handles for reading and writing data incrementally. They provide a unified interface across files, in-memory buffers, strings, and standard I/O — the same stream/read and stream/write work regardless of the underlying source.
;; Read a file line by line
(with-stream (s (stream/open-input "data.txt"))
(let loop ((line (stream/read-line s)))
(when line
(println line)
(loop (stream/read-line s)))))
;; In-memory buffer
(let ((buf (stream/byte-buffer)))
(stream/write-string buf "hello")
(stream/to-string buf)) ; => "hello"Creating Streams
stream/from-string
Create a read-only stream from a string's UTF-8 bytes.
(def s (stream/from-string "hello world"))
(stream/read-byte s) ; => 104 (ASCII 'h')
(stream/read s 5) ; => #u8(101 108 108 111 32) ("ello ")stream/from-bytes
Create a readable stream from a bytevector.
(def s (stream/from-bytes (bytevector 1 2 3)))
(stream/read-byte s) ; => 1
(stream/read-byte s) ; => 2stream/byte-buffer
Create a read/write in-memory buffer. Writes append to the buffer; reads consume from the current position.
(def buf (stream/byte-buffer))
(stream/write buf (string->utf8 "hello"))
(stream/to-string buf) ; => "hello"stream/open-input
Open a file for reading. Returns a buffered input stream. Sandbox-gated (FS_READ).
(def s (stream/open-input "data.csv"))
(def contents (stream/read-all s))
(stream/close s)stream/open-output
Open (or create) a file for writing. Returns a buffered output stream. Sandbox-gated (FS_WRITE).
(def s (stream/open-output "output.txt"))
(stream/write-string s "hello world\n")
(stream/close s)Reading
stream/read
Read up to n bytes, returning a bytevector. Returns fewer bytes at EOF.
(stream/read s 1024) ; => bytevector (up to 1024 bytes)stream/read-byte
Read a single byte. Returns an integer 0–255, or nil at EOF.
(stream/read-byte s) ; => 65 (or nil at EOF)stream/read-line
Read until newline (\n), returning a string without the newline. Strips trailing \r for Windows line endings. Returns nil at EOF.
(stream/read-line s) ; => "first line" (or nil)stream/read-all
Read the entire stream into a bytevector.
(def data (stream/read-all s))
(utf8->string data) ; convert to string if textWriting
stream/write
Write a bytevector. Returns the number of bytes written.
(stream/write s (bytevector 72 101 108 108 111)) ; => 5stream/write-byte
Write a single byte (integer 0–255).
(stream/write-byte s 10) ; write a newlinestream/write-string
Write a string as UTF-8 bytes. Returns the number of bytes written.
(stream/write-string s "hello") ; => 5Control
stream/close
Close a stream, releasing the underlying resource. Double-close is a no-op.
(stream/close s)
(stream/close s) ; safe, does nothingstream/flush
Flush any buffered output to the underlying sink.
(stream/flush s)stream/copy
Copy all bytes from one stream to another. Returns total bytes copied.
(with-stream (in (stream/open-input "src.bin"))
(with-stream (out (stream/open-output "dst.bin"))
(stream/copy in out))) ; => bytes copiedIntrospection
stream?
Type predicate — returns #t if the value is a stream.
(stream? (stream/byte-buffer)) ; => #t
(stream? 42) ; => #fstream/readable?, stream/writable?
Check the direction of a stream.
(stream/readable? (stream/from-string "x")) ; => #t
(stream/writable? (stream/from-string "x")) ; => #f
(stream/writable? (stream/byte-buffer)) ; => #tstream/available?
Returns #t if data is ready to read without blocking.
(stream/available? (stream/from-string "x")) ; => #t
(stream/available? (stream/from-string "")) ; => #fstream/type
Returns a string describing the stream implementation.
(stream/type (stream/byte-buffer)) ; => "byte-buffer"
(stream/type (stream/from-string "x")) ; => "string"
(stream/type (stream/open-input "f.txt")) ; => "file-input"
(stream/type *stdout*) ; => "stdout"Extraction (Byte Buffers)
stream/to-bytes
Extract the accumulated contents of a byte-buffer stream as a bytevector.
(let ((s (stream/byte-buffer)))
(stream/write s (bytevector 1 2 3))
(stream/to-bytes s)) ; => #u8(1 2 3)stream/to-string
Extract the contents of a byte-buffer stream as a UTF-8 string.
(let ((s (stream/byte-buffer)))
(stream/write-string s "hello")
(stream/to-string s)) ; => "hello"Standard I/O
Three global streams are available for console I/O:
| Stream | Direction | Description |
|---|---|---|
*stdin* | Readable | Standard input |
*stdout* | Writable | Standard output |
*stderr* | Writable | Standard error |
(stream/write-string *stdout* "prompt> ")
(stream/flush *stdout*)
(stream/write-string *stderr* "warning: something happened\n")Resource Management
with-stream
Macro that binds a stream, executes the body, and automatically closes the stream on exit — even if an error is thrown.
(with-stream (s (stream/open-input "data.txt"))
(stream/read-all s))
;; s is closed here, even if read-all threw an error
;; Write to a file
(with-stream (out (stream/open-output "output.txt"))
(stream/write-string out "line 1\n")
(stream/write-string out "line 2\n"))
;; file is flushed and closedPatterns
Line-by-Line Processing
(with-stream (s (stream/open-input "log.txt"))
(let loop ((line (stream/read-line s))
(count 0))
(if (nil? line)
count
(loop (stream/read-line s) (+ count 1)))))Building a String Incrementally
(let ((buf (stream/byte-buffer)))
(stream/write-string buf "{")
(stream/write-string buf "\"key\": \"value\"")
(stream/write-string buf "}")
(stream/to-string buf)) ; => "{\"key\": \"value\"}"File Copy
(with-stream (in (stream/open-input "photo.jpg"))
(with-stream (out (stream/open-output "backup.jpg"))
(stream/copy in out)))Error Handling
Reading a closed stream or writing to a read-only stream throws an error caught with try/catch:
(try
(let ((s (stream/from-string "x")))
(stream/close s)
(stream/read s 1)) ; throws "stream is closed"
(catch e
(println (str "Error: " e))))