Standard library · System and tools

os/error

The last error from a file or process operation: its code, its message and the path that failed.

import os.error

When an operation in the fs.file, fs.dir, os.process or os.temp packages fails, it records a short report here: a code, a message, the operation and the path. Call Clear, run the operation, then ask HasError and Last. It is a single global record, like C's errno, not a returned value.

The plain functions of the os package do not record anything here; they only return -1. This package is a different thing from the general-purpose error package, which builds error values to return from your own functions; prefer that for new code that is not about files or processes.

Know what the standard packages actually record before you branch on it:

CodeRecorded by
IOErrorAlmost every failure: fs.dir (Mkdir, MkdirAll, Remove, RemoveAll, Copy, Move), fs.file (AtomicWrite, Copy, Move) and os.temp
FileNotFoundOnly fs.file.Delete, for any failure to delete, a missing permission included
ProcessFailedos.process, when a program cannot be started
the othersNothing in the standard library yet; they are there for your own Set calls

Reads and plain writes such as fs.file.ReadText and WriteText record nothing; check their results instead. The messages are fixed English phrases such as delete failed, not the system's reason, and system_code is filled in only by os.process.

Check an operation and explain the failure

The usual pattern has three steps: call Clear, run the operation, then ask HasError. Last returns an OSError with the code to branch on, the path that failed and the name of the operation, which is enough to turn the failure into a message a person understands.

os-error-guide-explain.salam
import os.error
import fs.file

func explain(e: error.OSError): str:
    switch e.code:
        error.FileNotFound: ret "there is no such file: " + e.path end
        error.IOError: ret e.operation + " failed on " + e.path end
        error.ProcessFailed: ret "could not start " + e.path end
    end
    ret "something else went wrong in " + e.operation
end

func remove_draft(name: str):
    error.Clear()
    file.Delete(name)
    if error.HasError():
        println "could not delete:", explain(error.Last())
        ret
    end
    println "deleted", name
end

func main:
    file.WriteText("draft.txt", "notes")
    remove_draft("draft.txt")
    remove_draft("draft.txt")
end
Outputdeleted draft.txt could not delete: there is no such file: draft.txt

Compare code with the constants of this package, never message: the message is meant for people and may change between releases.

Errors stay until you clear them

A failed call records an error, but a successful call does not remove it. So HasError after a good call can still be true because of something that failed much earlier. Call Clear just before the operation you want to check.

os-error-guide-clear.salam
import os.error
import fs.file

func main:
    file.Delete("missing.txt")
    file.WriteText("ok.txt", "fine")
    println "after a good write:", error.HasError(), error.LastMessage()
    error.Clear()
    file.WriteText("ok.txt", "still fine")
    println "after Clear and a good write:", error.HasError()
    file.Delete("ok.txt")
    println "after deleting ok.txt:", error.HasError()
end
Outputafter a good write: true delete failed after Clear and a good write: false after deleting ok.txt: false

The record is a single global value for the whole program, so it is not a safe channel between threads: a failure in one thread can overwrite another thread's error before it is read. Check the error in the same thread, right after the call.

Report errors from your own functions

Set records an error the same way the standard packages do. A helper that reads files can use it, and its callers then check it with the same HasError and Last they use everywhere else. Pass the name of your function as the operation and the file as the path, so the message says where things went wrong.

os-error-guide-own.salam
import os.error
import fs.file

func load_port(path: str): int:
    error.Clear()
    if not file.Exists(path):
        error.Set(error.FileNotFound, "config file is missing", "load_port", path, 0)
        ret 0
    end
    text := file.ReadText(path).trim()
    port := text.to_int()
    if port <= 0 or port > 65535:
        error.Set(error.InvalidPath, "not a port number: " + text, "load_port", path, 0)
        ret 0
    end
    ret port
end

func show(path: str):
    port := load_port(path)
    if error.HasError():
        e := error.Last()
        println e.operation + "(" + e.path + "):", e.message
        ret
    end
    println path, "says port", port
end

func main:
    file.WriteText("good.conf", "8080\n")
    file.WriteText("bad.conf", "eighty\n")
    show("good.conf")
    show("bad.conf")
    show("none.conf")
    file.Delete("good.conf")
    file.Delete("bad.conf")
end
Outputgood.conf says port 8080 load_port(bad.conf): not a port number: eighty load_port(none.conf): config file is missing

A program that will not start

The os.process package records ProcessFailed here when it cannot start a program at all, for example when the path is wrong. That is different from a program that starts and then exits with a non-zero code: then there is no error here, and you read the code from Wait.

os-error-guide-process.salam
import os.error
import os.process

func main:
    mut args := Vector {} as Vector<str>
    defer args.free()
    args.push("--version")
    error.Clear()
    mut p := process.StartCaptured("/no/such/program", args)
    if error.HasError():
        e := error.Last()
        println "could not start:", e.code == error.ProcessFailed, e.operation, e.path
        ret 0
    end
    code := process.Wait(p)
    process.Cleanup(p)
    println "started, but it exited with", code
end
Outputcould not start: true StartCaptured /no/such/program

The system_code field holds the operating system's own number when the package knows it: the posix_spawnp result on Unix and GetLastError on Windows, from os.process only. Everywhere else it is zero. It is useful in logs; branch on code.

Types

OSError

struct OSError
The detail of an error.
NameTypeDescription
codeint = 0The error code; one of the constants above.
messagestr = ""A short fixed English phrase such as mkdir failed; it does not carry the system's reason.
operationstr = ""The name of the function that failed.
pathstr = ""The path that had the problem.
system_codeint = 0The operating system's own error number when known; only os.process fills it in, everywhere else it is 0.
os-error-Last.salam
import os.error
import fs.file

func main:
    error.Clear()
    println error.HasError()
    file.Delete("a file that does not exist.txt")
    if error.HasError():
        e := error.Last()
        println e.code == error.FileNotFound, e.operation, e.message
        println error.LastCode(), error.LastMessage()
    end
    error.Set(error.Timeout, "time ran out", "waiting", "", 0)
    println error.LastCode() == error.Timeout, error.LastMessage()
    println error.None, error.PermissionDenied, error.AlreadyExists, error.InvalidPath, error.NotSupported, error.IOError, error.ProcessFailed
end
Outputfalse true Delete delete failed 1 delete failed true time ran out 0 2 3 4 5 6 8

Constants and variables

None

const None: i32 = 0
No error.

The example for this item is shown with the ones above: os-error-Last.salam

FileNotFound

const FileNotFound: i32 = 1
The file or directory was not found. In the standard library only fs.file.Delete records it, for any failed delete.

The example for this item is shown with the ones above: os-error-Last.salam

PermissionDenied

const PermissionDenied: i32 = 2
Access is not allowed. No standard package records it yet; a denied operation shows up as IOError.

The example for this item is shown with the ones above: os-error-Last.salam

AlreadyExists

const AlreadyExists: i32 = 3
It is already there. Not recorded by the standard packages; for your own Set calls.

The example for this item is shown with the ones above: os-error-Last.salam

InvalidPath

const InvalidPath: i32 = 4
The path is not valid. Not recorded by the standard packages; for your own Set calls.

The example for this item is shown with the ones above: os-error-Last.salam

NotSupported

const NotSupported: i32 = 5
This system does not support the operation. Not recorded by the standard packages.

The example for this item is shown with the ones above: os-error-Last.salam

IOError

const IOError: i32 = 6
A read, write or file system error: the code most failures in fs.dir, fs.file and os.temp record.

The example for this item is shown with the ones above: os-error-Last.salam

Timeout

const Timeout: i32 = 7
Time ran out. Not recorded by the standard packages.

The example for this item is shown with the ones above: os-error-Last.salam

ProcessFailed

const ProcessFailed: i32 = 8
Running the process failed.

The example for this item is shown with the ones above: os-error-Last.salam

Functions

Set

func Set(code: int, message: str, operation: str, path: str, system_code: int)
Records an error, replacing the previous one, for when you are writing a package of your own and want to report through the same mechanism.

The example for this item is shown with the ones above: os-error-Last.salam

Clear

func Clear()
Clears the recorded error, before an operation whose error you want to check on its own.

The example for this item is shown with the ones above: os-error-Last.salam

Last

func Last(): OSError
The last recorded error with all its detail; a successful call does not reset it.

The example for this item is shown with the ones above: os-error-Last.salam

LastCode

func LastCode(): int
The code of the last error.

The example for this item is shown with the ones above: os-error-Last.salam

LastMessage

func LastMessage(): str
The message of the last error.

The example for this item is shown with the ones above: os-error-Last.salam

HasError

func HasError(): bool
True when an error has been recorded since the last Clear.

The example for this item is shown with the ones above: os-error-Last.salam