Silk

silk/filesystem

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc.

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, OutOfMemoryError }

import silk.effect { Effect }

import silk.filesystem { FileSystem, FileError, Path }

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

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

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

Import as FileSystem with import silk.filesystem { FileSystem }.

Public declarations: 10.

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.

Associated function Path.make

pub effect<'life0> fn make<'life0>(value: string<'life0>) -> 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(FileSystem.pathOperation(), FileSystem.invalidPath()); copying can fail with OutOfMemoryError.

Associated function Path.fromBytes

pub effect<'life0> fn fromBytes<'life0>(values: &'life0 [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.

Associated function Path.root

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

Allocates the portable root path / in the selected allocator.

Method Path.rawBytes

pub fn rawBytes<'life0>(self: &'life0 Path) -> &'life0 [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.

Method Path.view

pub fn view<'life0>(self: &'life0 Path) -> string<'life0>

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.

Method Path.isRoot

pub fn isRoot<'life0>(self: &'life0 Path) -> bool

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

Method Path.name

pub fn name<'life0>(self: &'life0 Path) -> string<'life0>

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.

Associated function Path.join

pub effect<'env> fn join<'life0: 'env, 'life1: 'env, 'env>(base: &'life0 silk/filesystem.Path, fragment: string<'life1>) -> 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.

Associated function Path.joinUtf8

pub effect<'env> fn joinUtf8<'life0: 'env, 'life1: 'env, 'env>(base: &'life0 silk/filesystem.Path, fragment: &'life1 [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.

Associated function Path.joinBytes

pub effect<'env> fn joinBytes<'life0: 'env, 'life1: 'env, 'env>(base: &'life0 silk/filesystem.Path, fragment: &'life1 [u8]) -> Path ! FileError | OutOfMemoryError ? &mut Allocator

Appends one normalized relative byte fragment while preserving non-UTF-8 names.

Details

The fragment must be nonempty and contain no NUL, empty, dot or dot-dot component. This is the byte-preserving operation for native directory names; use joinUtf8 when the caller also requires valid UTF-8.

Associated function Path.resolve

pub effect<'env> fn resolve<'life0: 'env, 'life1: 'env, 'env>(base: &'life0 silk/filesystem.Path, relativeText: string<'life1>) -> 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.

Method Path.parent

pub effect<'life0> fn parent<'life0>(self: &'life0 Path) -> silk/option.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.

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.

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.

writeFileWithParents composes parent creation and the final write using the same lexical provider. Supply both &mut FileSystem and &mut Allocator for this operation. Its directory creation is not transactional: a failed final write may leave newly created parents behind.

Operation readFile

effect<'life0> fn readFile<'life0>(path: &'life0 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<'env> fn writeFile<'life0: 'env, 'life1: 'env, 'env>(path: &'life0 silk/filesystem.Path, bytes: &'life1 [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<'life0> fn stat<'life0>(path: &'life0 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<'life0> fn listDirectory<'life0>(path: &'life0 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<'life0> fn createDirectory<'life0>(path: &'life0 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<'life0> fn removeFile<'life0>(path: &'life0 silk/filesystem.Path) -> () ! FileError ? &mut FileSystem

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

Operation removeDirectory

effect<'life0> fn removeDirectory<'life0>(path: &'life0 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<'env> fn createTemporaryDirectory<'life0: 'env, 'life1: 'env, 'env>(parent: &'life0 silk/filesystem.Path, prefix: &'life1 [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.

Associated function FileSystem.file

pub fn file() -> DirectoryEntryKind

Constructs the regular-file DirectoryEntryKind.

Associated function FileSystem.directory

pub fn directory() -> DirectoryEntryKind

Constructs the directory DirectoryEntryKind.

Associated function FileSystem.entryKindCode

pub fn entryKindCode(kind: DirectoryEntryKind) -> i32

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

Associated function FileSystem.fileInfo

pub fn fileInfo(byteLength: usize) -> FileInfo

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

Associated function FileSystem.directoryInfo

pub fn directoryInfo() -> DirectoryInfo

Constructs the fieldless portable directory metadata value.

Associated function FileSystem.directoryEntry

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

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

Associated function FileSystem.readFileOperation

pub fn readFileOperation() -> FileOperation

Selects the read-file operation.

Associated function FileSystem.writeFileOperation

pub fn writeFileOperation() -> FileOperation

Selects the write-file operation.

Associated function FileSystem.statOperation

pub fn statOperation() -> FileOperation

Selects the stat operation.

Associated function FileSystem.listDirectoryOperation

pub fn listDirectoryOperation() -> FileOperation

Selects the list-directory operation.

Associated function FileSystem.createDirectoryOperation

pub fn createDirectoryOperation() -> FileOperation

Selects the create-directory operation.

Associated function FileSystem.removeFileOperation

pub fn removeFileOperation() -> FileOperation

Selects the remove-file operation.

Associated function FileSystem.removeDirectoryOperation

pub fn removeDirectoryOperation() -> FileOperation

Selects the remove-directory operation.

Associated function FileSystem.pathOperation

pub fn pathOperation() -> FileOperation

Selects path construction and resolution.

Associated function FileSystem.createTemporaryDirectoryOperation

pub fn createTemporaryDirectoryOperation() -> FileOperation

Selects the create-temporary-directory operation.

Associated function FileSystem.operationCode

pub fn operationCode(operation: FileOperation) -> i32

Returns the stable numeric code of a consumed FileOperation.

Associated function FileSystem.notFound

pub fn notFound() -> FileReason

Constructs the NotFound recovery reason.

Associated function FileSystem.alreadyExists

pub fn alreadyExists() -> FileReason

Constructs the AlreadyExists recovery reason.

Associated function FileSystem.permissionDenied

pub fn permissionDenied() -> FileReason

Constructs the PermissionDenied recovery reason.

Associated function FileSystem.invalidPath

pub fn invalidPath() -> FileReason

Constructs the InvalidPath recovery reason.

Associated function FileSystem.wrongType

pub fn wrongType() -> FileReason

Constructs the WrongType recovery reason.

Associated function FileSystem.notEmpty

pub fn notEmpty() -> FileReason

Constructs the NotEmpty recovery reason.

Associated function FileSystem.noSpace

pub fn noSpace() -> FileReason

Constructs the NoSpace recovery reason.

Associated function FileSystem.tooLarge

pub fn tooLarge() -> FileReason

Constructs the TooLarge recovery reason.

Associated function FileSystem.unsupported

pub fn unsupported() -> FileReason

Constructs the Unsupported recovery reason.

Associated function FileSystem.other

pub fn other() -> FileReason

Constructs the catch-all Other recovery reason.

Associated function FileSystem.reasonCode

pub fn reasonCode(reason: FileReason) -> i32

Returns the stable numeric code of a consumed FileReason.

Associated function FileSystem.error

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

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

Associated function FileSystem.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.

Associated function FileSystem.providerCode

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

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

Associated function FileSystem.temporaryDirectory

pub effect<'env> fn temporaryDirectory<'life0: 'env, 'life1: 'env, 'env>(parent: &'life0 silk/filesystem.Path, prefix: string<'life1>) -> 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.

Associated function FileSystem.release

pub effect<'static> 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.

Associated function FileSystem.releaseIgnored

pub effect<'static> 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.

Associated function FileSystem.removeDirectoryRecursively

pub effect<'life0> fn removeDirectoryRecursively<'life0>(path: &'life0 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.

Associated function FileSystem.createDirectoriesRecursively

pub effect<'life0> fn createDirectoriesRecursively<'life0>(path: &'life0 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.

Associated function FileSystem.writeFileWithParents

pub effect<'env> fn writeFileWithParents<'life0: 'env, 'life1: 'env, 'env>(path: &'life0 silk/filesystem.Path, bytes: &'life1 [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.

Associated function FileSystem.exists

pub effect<'life0> fn exists<'life0>(path: &'life0 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.

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.

On this page

When to useDetailsGotchasExamplesConstruct and inspect a portable pathPathDetailsAssociated function Path.makeDetailsAssociated function Path.fromBytesDetailsAssociated function Path.rootMethod Path.rawBytesDetailsMethod Path.viewDetailsMethod Path.isRootMethod Path.nameDetailsAssociated function Path.joinDetailsAssociated function Path.joinUtf8DetailsAssociated function Path.joinBytesDetailsAssociated function Path.resolveDetailsMethod Path.parentDetailsFileInfoField byteLengthDirectoryInfoDirectoryEntryKindField codeDirectoryEntryField pathField kindFileOperationField codeFileReasonField codeFileErrorDetailsField operationField reasonFileSystemDetailsOperation readFileDetailsOperation writeFileDetailsOperation statDetailsOperation listDirectoryDetailsOperation createDirectoryDetailsOperation removeFileOperation removeDirectoryDetailsOperation createTemporaryDirectoryDetailsAssociated function FileSystem.fileAssociated function FileSystem.directoryAssociated function FileSystem.entryKindCodeAssociated function FileSystem.fileInfoAssociated function FileSystem.directoryInfoAssociated function FileSystem.directoryEntryAssociated function FileSystem.readFileOperationAssociated function FileSystem.writeFileOperationAssociated function FileSystem.statOperationAssociated function FileSystem.listDirectoryOperationAssociated function FileSystem.createDirectoryOperationAssociated function FileSystem.removeFileOperationAssociated function FileSystem.removeDirectoryOperationAssociated function FileSystem.pathOperationAssociated function FileSystem.createTemporaryDirectoryOperationAssociated function FileSystem.operationCodeAssociated function FileSystem.notFoundAssociated function FileSystem.alreadyExistsAssociated function FileSystem.permissionDeniedAssociated function FileSystem.invalidPathAssociated function FileSystem.wrongTypeAssociated function FileSystem.notEmptyAssociated function FileSystem.noSpaceAssociated function FileSystem.tooLargeAssociated function FileSystem.unsupportedAssociated function FileSystem.otherAssociated function FileSystem.reasonCodeAssociated function FileSystem.errorAssociated function FileSystem.errorWithCodeDetailsAssociated function FileSystem.providerCodeAssociated function FileSystem.temporaryDirectoryDetailsAssociated function FileSystem.releaseDetailsAssociated function FileSystem.releaseIgnoredDetailsAssociated function FileSystem.removeDirectoryRecursivelyDetailsAssociated function FileSystem.createDirectoriesRecursivelyDetailsAssociated function FileSystem.writeFileWithParentsDetailsAssociated function FileSystem.existsDetailsTemporaryDirectoryDetailsField path