Silk

silk/filesystem

Portable normalized paths, whole-file operations, directory traversal, and explicit temp scopes.

When to use

Build provider-absolute Path values with make or fromBytes, then run operations through a supplied FileSystem. Use rawBytes for platform values that must round-trip even when they are not UTF-8, and resolve for lexical relative-path resolution.

Details

Paths are absolute and normalized: they reject NUL, empty components, trailing separators, and embedded . or ... Resolution handles relative dot components but rejects escape above root. Directory listings return independently owned child paths in deterministic path-byte order. Portable FileError data names both the operation and a closed recovery reason, with an optional provider code for diagnostics.

Temporary directories have an explicit lifecycle because removal can fail and needs services. Use release when cleanup failure matters, or releaseIgnored as an infallible finalizer only after deliberately accepting that loss.

Gotchas

A path created from arbitrary bytes may not have a valid text view. Keep using rawBytes unless the bytes were validated as UTF-8; view and name rely on that caller knowledge.

Examples

Construct and inspect a portable path

import silk.allocator { Allocator }

import silk.effect { Effect }

import silk.filesystem as FileSystem

effect fn example() -> i32
! FileSystem.FileError | Allocator.OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  let path = run FileSystem.make("/workspace")
    |> Effect.provideMut(&mut allocator)
  if FileSystem.name(&path) == "workspace" {
    return 42
  }
  return 0
}

effect fn recover(error: FileSystem.FileError | Allocator.OutOfMemoryError) -> i32 {
  return 0
}

pub fn main() -> i32 {
  return run Effect.catchAll(example(), recover)
}

Import as FileSystem with import silk.filesystem.

Public declarations: 58.

Path

pub struct Path

An owned, normalized absolute path in a FileSystem provider's portable namespace.

Details

Portable / means the selected provider's root, not necessarily the host operating system's root. Construct paths through make, fromBytes, root, join, or resolve; the representation is private so every Path satisfies the normalization rules.

FileInfo

pub struct FileInfo

Minimal portable metadata for one regular file.

Field byteLength

pub byteLength: usize

Complete file length in bytes.

DirectoryInfo

pub struct DirectoryInfo

Portable metadata identifying a directory; no platform-specific fields are exposed.

DirectoryEntryKind

pub struct DirectoryEntryKind

The closed portable kind of one directory entry.

Field code

pub code: i32

Stable portable kind code selected by file or directory.

DirectoryEntry

pub struct DirectoryEntry

One immediate directory child with an independently owned complete Path.

Field path

pub path: Path

Independently owned complete path to the child.

Field kind

pub kind: DirectoryEntryKind

Portable kind reported for the child.

FileOperation

pub struct FileOperation

The stable portable operation category stored in a FileError.

Field code

pub code: i32

Stable code identifying the attempted portable operation.

FileReason

pub struct FileReason

A stable portable recovery category stored in a FileError.

Field code

pub code: i32

Stable code identifying the portable recovery reason.

FileError

pub struct FileError

An allocation-free portable failure naming the attempted operation and recovery reason.

Details

Match or compare operationCode and reasonCode for portable recovery. providerCode may retain an OS or provider-specific numeric detail for diagnostics, but portable decisions must not depend on it.

Field operation

pub operation: FileOperation

The operation that failed.

Field reason

pub reason: FileReason

The portable reason callers can recover by.

file

pub fn file() -> DirectoryEntryKind

Constructs the regular-file DirectoryEntryKind.

directory

pub fn directory() -> DirectoryEntryKind

Constructs the directory DirectoryEntryKind.

entryKindCode

pub fn entryKindCode(kind: DirectoryEntryKind) -> i32

Returns the stable code for a consumed DirectoryEntryKind: 0 for file, 1 for directory.

fileInfo

pub fn fileInfo(byteLength: usize) -> FileInfo

Constructs regular-file metadata with the complete length in bytes.

directoryInfo

pub fn directoryInfo() -> DirectoryInfo

Constructs the fieldless portable directory metadata value.

directoryEntry

pub fn directoryEntry(path: Path, kind: DirectoryEntryKind) -> DirectoryEntry

Constructs a directory entry by taking ownership of its complete child path and kind.

readFileOperation

pub fn readFileOperation() -> FileOperation

Selects the read-file operation.

writeFileOperation

pub fn writeFileOperation() -> FileOperation

Selects the write-file operation.

statOperation

pub fn statOperation() -> FileOperation

Selects the stat operation.

listDirectoryOperation

pub fn listDirectoryOperation() -> FileOperation

Selects the list-directory operation.

createDirectoryOperation

pub fn createDirectoryOperation() -> FileOperation

Selects the create-directory operation.

removeFileOperation

pub fn removeFileOperation() -> FileOperation

Selects the remove-file operation.

removeDirectoryOperation

pub fn removeDirectoryOperation() -> FileOperation

Selects the remove-directory operation.

pathOperation

pub fn pathOperation() -> FileOperation

Selects path construction and resolution.

createTemporaryDirectoryOperation

pub fn createTemporaryDirectoryOperation() -> FileOperation

Selects the create-temporary-directory operation.

operationCode

pub fn operationCode(operation: FileOperation) -> i32

Returns the stable numeric code of a consumed FileOperation.

notFound

pub fn notFound() -> FileReason

Constructs the NotFound recovery reason.

alreadyExists

pub fn alreadyExists() -> FileReason

Constructs the AlreadyExists recovery reason.

permissionDenied

pub fn permissionDenied() -> FileReason

Constructs the PermissionDenied recovery reason.

invalidPath

pub fn invalidPath() -> FileReason

Constructs the InvalidPath recovery reason.

wrongType

pub fn wrongType() -> FileReason

Constructs the WrongType recovery reason.

notEmpty

pub fn notEmpty() -> FileReason

Constructs the NotEmpty recovery reason.

noSpace

pub fn noSpace() -> FileReason

Constructs the NoSpace recovery reason.

tooLarge

pub fn tooLarge() -> FileReason

Constructs the TooLarge recovery reason.

unsupported

pub fn unsupported() -> FileReason

Constructs the Unsupported recovery reason.

other

pub fn other() -> FileReason

Constructs the catch-all Other recovery reason.

reasonCode

pub fn reasonCode(reason: FileReason) -> i32

Returns the stable numeric code of a consumed FileReason.

error

pub fn error(operation: FileOperation, reason: FileReason) -> FileError

Constructs a portable FileError without a provider-specific numeric detail.

errorWithCode

pub fn errorWithCode(operation: FileOperation, reason: FileReason, code: i32) -> FileError

Constructs a portable FileError while retaining one provider-specific diagnostic code.

Details

The numeric code is opaque outside that provider. The portable operation and reason remain the fields callers should use for recovery.

providerCode

pub fn providerCode(error: &silk/filesystem.FileError) -> Option<i32>

Borrows an error and returns its provider-specific numeric detail, if one was retained.

make

pub effect fn make(value: string) -> Path ! FileError | OutOfMemoryError ? &mut Allocator

Copies UTF-8 text into an owned, normalized provider-absolute Path.

Details

The text must begin with /. Root is valid; every other path must have nonempty components and no trailing slash, NUL, . component, or .. component. Invalid input fails with FileError(pathOperation(), invalidPath()); copying can fail with OutOfMemoryError.

fromBytes

pub effect fn fromBytes(values: &[u8]) -> Path ! FileError | OutOfMemoryError ? &mut Allocator

Constructs an owned normalized provider-absolute Path from exact platform bytes.

Details

Platform paths are byte sequences, and a caller that received one from the platform — a directory entry, an argument, an environment value — must be able to hand it back unchanged. The same normalization applies as for textual construction: the value is absolute, rejects NUL, and rejects ., .., empty components, and trailing separators. Well-formed text is not required, so a Path built this way may have no string view.

root

pub effect fn root() -> Path ! OutOfMemoryError ? &mut Allocator

Allocates the portable root path / in the selected allocator.

rawBytes

pub fn rawBytes(self: &silk/filesystem.Path) -> &[u8]

Borrows the complete normalized path as exact platform bytes.

Details

This is the lossless view. It round-trips a Path built from platform bytes even when those bytes are not well-formed text, which the string view cannot promise.

view

pub fn view(self: &silk/filesystem.Path) -> string

Borrows the complete path as text when its bytes are known to be valid UTF-8.

Details

Paths from make, join, joinUtf8, and resolve satisfy that precondition. A path created with fromBytes may not; use rawBytes unless the source bytes were validated.

isRoot

pub fn isRoot(self: &silk/filesystem.Path) -> bool

Returns true exactly when this path is the portable root /.

name

pub fn name(self: &silk/filesystem.Path) -> string

Borrows the final component as text; root returns empty text.

Details

This has the same UTF-8 precondition as view. It does not allocate or include a separator.

join

pub effect fn join(base: &silk/filesystem.Path, fragment: string) -> Path ! FileError | OutOfMemoryError ? &mut Allocator

Appends one normalized relative text fragment to an absolute base path.

Details

fragment must be nonempty and relative, with no NUL, empty, ., or .. component and no trailing slash. Use resolve when dot components should be interpreted instead of rejected.

joinUtf8

pub effect fn joinUtf8(base: &silk/filesystem.Path, fragment: &[u8]) -> Path ! FileError | OutOfMemoryError ? &mut Allocator

Validates UTF-8 bytes as one normalized relative fragment and appends them to base.

Details

This is useful for a child name returned as bytes by another portable API. Invalid UTF-8 and the same malformed components rejected by join fail with the InvalidPath reason.

resolve

pub effect fn resolve(base: &silk/filesystem.Path, relativeText: string) -> Path ! FileError | OutOfMemoryError ? &mut Allocator

Resolves relative text lexically against an explicit absolute base.

Details

Empty text and . keep the base; .. removes components; ordinary components append. An absolute relative value, an empty interior component, NUL, or any attempt to escape above root fails with the InvalidPath reason. Resolution is lexical and never accesses the filesystem.

parent

pub effect fn parent(self: &silk/filesystem.Path) -> Option<silk/filesystem.Path> ! OutOfMemoryError ? &mut Allocator

Allocates an independently owned parent path, or None when self is root.

Details

The result does not borrow self. A direct child of root has root as its parent.

FileSystem

pub service FileSystem

Portable mutable service for normalized paths and whole-file operations.

Details

Application code supplies one provider lexically with Effect.provideMut; tests can implement this service in memory, while native applications can use silk.os_filesystem. The service owns platform policy, but every implementation must preserve the portable error categories, create-or-truncate writes, and deterministic listing order described here.

Examples

Write a file after creating its parents

import silk.allocator { Allocator }

import silk.filesystem as FileSystem

import silk.usize as usize

pub effect fn store(path: &FileSystem.Path, contents: &[u8]) -> usize
! FileSystem.FileError | Allocator.OutOfMemoryError
? &mut FileSystem.FileSystem | &mut Allocator {
  let written = run FileSystem.writeFileWithParents(path, contents)
  return contents.length
}

Operation readFile

effect fn readFile(path: &silk/filesystem.Path) -> Bytes ! FileError | OutOfMemoryError ? &mut FileSystem | &mut Allocator

Reads one complete regular file into independently owned bytes.

Details

Reading a directory fails with WrongType. Allocation of the returned Bytes may fail independently of the provider read.

Operation writeFile

effect fn writeFile(path: &silk/filesystem.Path, bytes: &[u8]) -> () ! FileError ? &mut FileSystem

Writes one complete byte view with create-or-truncate semantics.

Details

A missing file is created; an existing regular file is replaced by exactly bytes. The call does not create missing parent directories—use writeFileWithParents for that workflow.

Operation stat

effect fn stat(path: &silk/filesystem.Path) -> silk/filesystem.DirectoryInfo | silk/filesystem.FileInfo ! FileError ? &mut FileSystem

Returns FileInfo or DirectoryInfo for the path without opening file contents.

Details

Missing paths fail with NotFound; providers use WrongType only when an operation requires a particular kind, not for this discriminating query.

Operation listDirectory

effect fn listDirectory(path: &silk/filesystem.Path) -> silk/vector.Vector<silk/filesystem.DirectoryEntry> ! FileError | OutOfMemoryError ? &mut FileSystem | &mut Allocator

Returns immediate owned children in deterministic complete-path byte order.

Details

The result is not recursive. Each DirectoryEntry.path is independently owned and may be retained after the listing vector is released.

Operation createDirectory

effect fn createDirectory(path: &silk/filesystem.Path) -> () ! FileError ? &mut FileSystem

Creates exactly one missing directory whose parent already exists.

Details

Existing paths fail with AlreadyExists; use createDirectoriesRecursively to ensure every missing component.

Operation removeFile

effect fn removeFile(path: &silk/filesystem.Path) -> () ! FileError ? &mut FileSystem

Removes exactly one regular file and fails with WrongType for a directory.

Operation removeDirectory

effect fn removeDirectory(path: &silk/filesystem.Path) -> () ! FileError ? &mut FileSystem

Removes exactly one empty directory.

Details

A nonempty directory fails with NotEmpty; use removeDirectoryRecursively only when all descendants are intentionally in scope for removal.

Operation createTemporaryDirectory

effect fn createTemporaryDirectory(parent: &silk/filesystem.Path, prefix: &[u8]) -> Path ! FileError | OutOfMemoryError ? &mut FileSystem | &mut Allocator

Creates one directory under an existing parent under a name no other caller holds.

Details

The provider chooses the name's unique part and returns the complete Path, because only the provider can create and claim a name in one step. A caller that supplied the name would have to check-then-create, and the gap between those two is exactly the race this avoids. prefix is a byte prefix for the provider-chosen child name, not a complete path. The returned directory already exists and is an immediate child of parent.

TemporaryDirectory

pub struct TemporaryDirectory

A directory a caller owns outright, together with everything written inside it.

Details

Ownership is affine: TemporaryDirectory holds an owned Path, so exactly one binding holds it and the compiler rejects a second use of a moved one. Ownership is not, however, a Drop hook. Removing a directory is a fallible operation that requires the FileSystem capability, and a Drop hook may carry neither a failure row nor a requirement row, so a hook here could only be written by inventing an infallible intrinsic over a fallible syscall. Release is therefore explicit and honest about both rows — see release.

Scope ownership comes from composition rather than from a hook: Effect.ensuring(release) runs the release whatever the protected Effect's outcome. Because ensuring types its finalizer ! never, that composition has to say what a failed removal means; releaseIgnored is the stdlib's answer and names the loss at the call site.

Field path

pub path: Path

The complete owned path callers use while the scope remains live.

temporaryDirectory

pub effect fn temporaryDirectory(parent: &silk/filesystem.Path, prefix: string) -> TemporaryDirectory ! FileError | OutOfMemoryError ? &mut FileSystem | &mut Allocator

Creates an explicitly owned temporary directory under parent with a name beginning in prefix.

Details

The result is owned. Nothing removes it until a caller runs release or releaseIgnored. The prefix is encoded as UTF-8 and the provider chooses and claims the remaining unique name in one operation.

release

pub effect fn release(self: TemporaryDirectory) -> () ! FileError | OutOfMemoryError ? &mut FileSystem | &mut Allocator

Consumes one TemporaryDirectory and removes it together with everything inside it.

Details

Both rows are stated rather than hidden. Removal reaches the provider, so it can fail; walking the tree to find what to remove allocates, so it can exhaust memory. A caller that must observe a failed cleanup uses this operation and handles the failure. The owner is consumed even when removal fails, so copy any diagnostic path information needed before calling.

releaseIgnored

pub effect fn releaseIgnored(self: TemporaryDirectory) -> () ? &mut FileSystem | &mut Allocator

Consumes one TemporaryDirectory, removes it, and discards a failed removal.

Details

This exists because Effect.ensuring types its finalizer ! never, so a fallible release has to be recovered before it can be a finalizer. The recovery is deliberate and it is named: a caller reading releaseIgnored at the call site can see that a failed removal is being dropped, which a hook doing the same thing invisibly could not show. What is lost is bounded — a directory the host will reap — and what is kept is the protected Effect's own outcome, which is the answer the program was computing.

A caller who needs the failure uses release instead and does not compose it with ensuring.

The finalizer consumes the directory. The protected Effect cannot borrow it when the finalizer starts. Derive the required paths before you give the owner to the finalizer.

removeDirectoryRecursively

pub effect fn removeDirectoryRecursively(path: &silk/filesystem.Path) -> () ! FileError | OutOfMemoryError ? &mut FileSystem | &mut Allocator

Removes a directory, every descendant file, and every descendant directory.

Details

Two passes, because the portable primitive removes exactly one empty directory. The first pass walks the tree front to back, unlinking every file it meets and recording every directory it meets; the second removes the recorded directories back to front. That order is child-before-parent for free: a directory is always recorded before the children found inside it, so reversing the record reverses the containment. Neither pass recurses, so depth costs vector capacity rather than stack.

This operation is destructive and not transactional. If a provider or allocation failure occurs, removals already completed remain completed and the remaining tree is left in place.

createDirectoriesRecursively

pub effect fn createDirectoriesRecursively(path: &silk/filesystem.Path) -> () ! FileError | OutOfMemoryError ? &mut FileSystem | &mut Allocator

Ensures that path and every missing ancestor exist as directories.

Details

Existing directories are kept. An existing regular file at any component fails with WrongType; failures other than NotFound propagate. This is ordinary stat-then-create composition, so concurrent namespace changes may still race according to provider policy.

writeFileWithParents

pub effect fn writeFileWithParents(path: &silk/filesystem.Path, bytes: &[u8]) -> () ! FileError | OutOfMemoryError ? &mut FileSystem | &mut Allocator

Ensures every parent directory exists, then writes the complete byte view to path.

Details

The final write uses FileSystem.writeFile create-or-truncate semantics. Passing root delegates directly to the provider and normally fails with WrongType. Directory creation and writing are not transactional, so a later failure may leave newly created parents behind.

exists

pub effect fn exists(path: &silk/filesystem.Path) -> bool ! FileError ? &mut FileSystem

Returns whether a file or directory exists at path.

Details

Only the portable NotFound reason becomes false. Permission, I/O, and every other provider failure propagate so callers cannot mistake an inaccessible path for an absent one.

On this page