Skip to content

A way to narrow error set types (aka the opposite of ||) #19008

Closed as not planned
@squeek502

Description

@squeek502

Motivation

When working with explicit error sets, occasionally I'd like to be able to narrow an existing error set type. A simple example for demonstration:

pub const FooError = error{
    A,
    /// Windows-only 
    B,
};

pub fn foo(v: u8) FooError!void {
    // ...
}

// Would like to narrow this to exclude `error.B`
pub const BarError = FooError;

pub fn bar(v: u8) BarError!void {
    if (builtin.os.tag == .windows) @compileError("not supported on Windows");

    // Calls foo but we now know that error.B is unreachable
    return foo(v) catch |err| switch (err) {
        // Narrow the returned errors to exclude B
        error.B => unreachable,
        else => |e| return e,
    };
}

In this example, it would be nice to be able to narrow BarError to exclude error.B while still being able to define it in terms of FooError.

For a real world example, see here:

// This is os.ReadLinkError || os.RealPathError with impossible errors excluded
pub const SelfExePathError = error{
    FileNotFound,
    AccessDenied,
    NameTooLong,
    NotSupported,
    NotDir,
    // ... truncated ...
} || os.SysCtlError;

where in order to exclude a few errors like error.InvalidWtf8, error.InvalidUtf8, the os.ReadLinkError || os.RealPathError error set type had to be redefined from scratch.

The proposal

This proposal is for some mechanism to exclude errors from an error set type--essentially the opposite of the || operator. Another operator would likely make the most sense, something like ^^.

In the above examples, this would mean BarError could be defined like so:

pub const BarError = FooError ^^ error{B};

and SelfExePathError could be defined like this:

pub const SelfExePathError = os.ReadLinkError || os.RealPathError || os.SysCtlError ^^ error{
    InvalidWtf8, // selfExePath only converts from WTF-16 -> WTF-8 which cannot fail
    InvalidUtf8, // selfExePath is not supported on WASI
};

In terms of implementation, I'd imagine it would respect normal order of operations, e.g.

error{ A, B } || error{B} ^^ error{B} // -> error{A}
error{ A, B } ^^ error{B} || error{B} // -> error{ A, B }

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalThis issue suggests modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions