Docs/Files & system

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#

Valk
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
Valk
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#

Valk
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:

Valk
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:

Valk
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:

Valk
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
Valk
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:

Valk
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:

Valk
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:

Valk
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.