Files & streams
Read, write, and stream files.
On this page
valk.fs reads and writes files, and valk.io has the reader and writer interfaces that files, sockets and memory buffers share. For paths and directories, see Paths & directories.
Read or write a whole file#
use valk.fs
fn main() {
fs.write("notes.txt", "First note\n") ! return
fs.write("notes.txt", "Second note\n", true) ! return // append
let text = fs.read("notes.txt") ! {
println("Could not read notes.txt")
return
}
print(text)
}
fs.write creates the file when it is missing and replaces its contents unless the third argument is true. fs.read returns the whole file as a String. Both throw an io.IoError such as open or access.
Copy, move and delete#
| Function | Does |
|---|---|
fs.exists(path) |
Whether anything exists at path |
fs.is_file(path) / fs.is_dir(path) |
Whether it is a file or a directory |
fs.copy(from, to) |
Copies a file; pass true as third argument to copy a directory |
fs.move(from, to) |
Moves or renames |
fs.delete_file(path) |
Deletes a file |
fs.truncate(path, length) |
Cuts a file off or pads it with zeros |
fs.symlink(link, target) / fs.read_link(path) |
Creates or reads a symbolic link |
use valk.fs
fn main() {
fs.write("a.txt", "hello") ! return
fs.copy("a.txt", "b.txt") ! return
fs.move("b.txt", "c.txt") ! return
println(fs.exists("b.txt")) // false
fs.delete_file("c.txt") ! return
}
File info#
use valk.fs
fn main() {
fs.write("data.bin", "12345") ! return
let info = fs.stat("data.bin") ! return
println(info.size) // 5
println(info.kind == fs.FileKind.file) // true
}
FileInfo has size, kind (file, directory or other), permissions and modified_time in nanoseconds since 1970. The shortcuts fs.size(path) and fs.modified_time(path) return one field; fs.chmod(path, 0c600) and fs.set_modified_time(path, ns) change them.
Open a stream#
fs.stream opens a FileStream that reads and writes at a position. It opens read-only by default; OpenOptions asks for more:
use valk.fs
fn main() {
let file = fs.stream("log.txt", fs.OpenOptions { write: fs.WriteMode.truncate, create: true }) ! return
file.write("started\n") ! return
file.seek(0) ! return
let buffer = [u8]{ 0 x 64 }
let count = file.read(buffer) ! return
print(buffer.view(0, count).to_string()) // started
file.close() ! return
}
| Option | Meaning |
|---|---|
read |
Allow reading (default true) |
write |
null (no writing), WriteMode.preserve, WriteMode.truncate (clear on open) or WriteMode.append |
create |
Create the file when missing |
exclusive |
With create, fail when the file exists |
permissions |
Mode bits of a new file |
seek(offset, from) takes io.SeekFrom.start, current or end, and position is the current offset. sync() flushes the data to disk.
Read into a buffer#
A read fills a buffer you pass and returns how many bytes it filled, which may be fewer than the buffer holds. Zero means the end:
use valk.fs
fn main() {
fs.write("big.txt", "x".repeat(10000)) ! return
let file = fs.stream("big.txt") ! return
defer file.close() _
let buffer = [u8]{ 0 x 4096 }
let total: uint = 0
while true {
let count = file.read(buffer) ! return
if count == 0 : break
total += count
}
println(total) // 10000
}
defer file.close() _ closes the file on every way out of the function and ignores a close error. A fixed array works as the buffer too: let buffer: [u8 x 4096] = @undefined and file.read(&buffer).
Read lines#
io.LineReader reads any reader line by line. read_line() drops the \n or \r\n and returns null at the end:
use valk.fs
use valk.io
fn main() {
fs.write("todo.txt", "milk\nbread\n") ! return
let file = fs.stream("todo.txt") ! return
defer file.close() _
let lines = io.LineReader.new(file)
while true {
let line = lines.read_line() ! return
if !isset(line) : break
println("- " + line)
}
}
read_until(byte) splits on another byte and lines() returns everything left as an array.
Readers and writers#
io.Reader has read(buf) and io.Writer has write(data). Code written against them works with files, sockets, ByteBuffer, process pipes and the standard streams.
| Function | Does |
|---|---|
io.copy(reader, writer) |
Copies until the reader ends |
io.read_all(reader) |
Reads everything into a ByteBuffer |
fs.read_into(path, writer) |
Streams a file into a writer |
fs.write_from(path, reader) |
Streams a reader into a file |
io.stdin(), io.stdout(), io.stderr() |
The standard streams |
"text".reader() |
A reader over a string |
use valk.fs
use valk.io
fn main() {
fs.write_from("copy.txt", "hello from a reader\n".reader()) ! return
fs.read_into("copy.txt", io.stdout()) ! return // hello from a reader
io.stderr().write("done\n") ! return
}
Build bytes in memory#
ByteBuffer grows as you write to it and is itself an io.Writer:
fn main() {
let out = ByteBuffer.new()
out.write("Total: ")
out.write_f64_ascii(12.5, 2)
out.write_byte(' ')
out.write_uint_ascii(255, 16)
println(out.to_string()) // Total: 12.50 FF
println(out.length) // 15
}
| Method | Writes |
|---|---|
write(data), write_byte(b) |
Bytes or text |
write_int_ascii(v, base), write_uint_ascii(v, base) |
A number as text |
write_f64_ascii(v, decimals), write_f64_ascii_shortest(v) |
A float as text |
write_u16_le, write_u32_be, write_u64_le, ... |
Binary integers |
write_f64_le, write_f64_be |
A binary float |
to_string() copies the contents, view() borrows them, clear() empties the buffer, and reader() reads them back.
Read binary data#
ByteReader walks over bytes, a String or a ByteBuffer:
fn main() {
let data = ByteBuffer.new()
data.write_u16_le(7)
data.write_u32_be(1000)
data.write("name")
let reader = data.reader()
println(reader.read_u16_le()) // 7
println(reader.read_u32_be()) // 1000
println(reader.read_remaining_string()) // name
}
Lock a file#
fs.lock takes a lock that other processes using fs.lock (or flock) respect, and creates the file when it is missing:
use valk.fs
fn main() {
let held = fs.lock("build.lock") ! {
println("Could not lock")
return
}
defer held.unlock() _
println("Working while holding the lock")
}
fs.lock(path, false) takes a shared lock, fs.lock(path, true, 500) gives up after 500 ms with timeout, and fs.try_lock(path) returns null instead of waiting. Waiting only pauses the current coroutine. The lock does not block plain reads and writes.

