nil Implementation Plan
Status: Planned
Priority: High (foundational semantics)
Effort: Small (1-2 hours)
Overview
Quartz currently has true and false but nil is documented but not implemented. This plan adds nil as a first-class keyword while also providing Option[T] for explicit optionality.
Design Decision
Both: nil + Option types — the world-class hybrid approach.
Runtime Model
┌─────────────────────────────────────────────────────────────────────────────┐
│ Quartz's Truth & Optionality Model │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ LAYER 1: Runtime Values (all i64) │
│ ═════════════════════════════════ │
│ │
│ nil = 0 # "nothing" / null pointer │
│ false = 0 # boolean false │
│ true = 1 # boolean true │
│ 0 = 0 # integer zero │
│ │
│ All three (nil, false, 0) are the SAME at runtime. │
│ This is intentional — Quartz is a systems language. │
│ │
│ LAYER 2: Truthiness (for conditionals) │
│ ══════════════════════════════════════ │
│ │
│ Falsy: 0, false, nil (all equivalent) │
│ Truthy: everything else (non-zero) │
│ │
│ if value # truthy check │
│ ... │
│ end │
│ │
│ LAYER 3: Compile-Time Types (rich, existential) │
│ ════════════════════════════════════════════════ │
│ │
│ Bool # true or false │
│ Int # any integer │
│ Option[T] # Some(value) or None — explicit optionality │
│ Result[T,E] # Ok(value) or Err(error) — explicit fallibility │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The Philosophy
- nil is for quick-and-dirty: “I don’t have a value”
- Option[T] is for when you want the compiler to enforce handling
- You can always go from explicit → implicit (Option → nil)
- But you can’t go implicit → explicit without checking
Usage Examples
Quick and Dirty (nil-based)
def find_user(id: Int): User
if id == 0
return nil # No user found
end
User { id: id, name: "Alice" }
end
user = find_user(42)
if user
print(user.name)
end
Explicit and Safe (Option-based)
def find_user_safe(id: Int): Option[User]
if id == 0
return Option::None
end
Option::Some(User { id: id, name: "Alice" })
end
match find_user_safe(42)
Option::Some(user) => print(user.name)
Option::None => print("Not found")
end
Optional Chaining (future)
# If we add ?. operator
user?.profile?.avatar?.url
Implementation Steps
Phase 1: Add nil keyword (C bootstrap first)
| Step | File | Change |
|---|---|---|
| 1 | quartz-bootstrap/src/lexer.c | Add TOK_NIL token type |
| 2 | quartz-bootstrap/src/lexer.c | Recognize “nil” as keyword |
| 3 | quartz-bootstrap/src/parser.c | Parse nil as literal (like true/false) |
| 4 | quartz-bootstrap/src/typecheck.c | Type nil as Nil (compatible with any ref type) |
| 5 | quartz-bootstrap/src/codegen.c | Emit 0 for nil literal |
Phase 2: Add nil to Quartz self-hosted compiler
| Step | File | Change |
|---|---|---|
| 1 | self-hosted/frontend/lexer.qz | Add TOK_NIL |
| 2 | self-hosted/frontend/parser.qz | Parse nil literal |
| 3 | self-hosted/middle/typecheck.qz | Type nil |
| 4 | self-hosted/backend/codegen.qz | Emit 0 for nil |
Phase 3: Add Option[T] to stdlib
# std/core/option.qz
enum Option[T]
Some(value: T)
None
end
def option_is_some(opt: Option[T]): Bool
match opt
Option::Some(_) => true
Option::None => false
end
end
def option_unwrap(opt: Option[T]): T
match opt
Option::Some(v) => v
Option::None => panic("unwrap on None")
end
end
def option_unwrap_or(opt: Option[T], default: T): T
match opt
Option::Some(v) => v
Option::None => default
end
end
def option_map(opt: Option[T], f: fn(T): U): Option[U]
match opt
Option::Some(v) => Option::Some(f(v))
Option::None => Option::None
end
end
Phase 4: Add Result[T, E] to stdlib
# std/core/result.qz
enum Result[T, E]
Ok(value: T)
Err(error: E)
end
def result_is_ok(res: Result[T, E]): Bool
match res
Result::Ok(_) => true
Result::Err(_) => false
end
end
def result_unwrap(res: Result[T, E]): T
match res
Result::Ok(v) => v
Result::Err(e) => panic("unwrap on Err")
end
end
def result_map(res: Result[T, E], f: fn(T): U): Result[U, E]
match res
Result::Ok(v) => Result::Ok(f(v))
Result::Err(e) => Result::Err(e)
end
end
Tests to Create
# spec/integration/nil_spec.rb
describe 'nil keyword' do
it 'nil is falsy' do
result = compile_and_run(<<~BE)
def main(): Int
x = nil
if x
return 1
end
return 0
end
BE
expect(result.exit_code).to eq(0)
end
it 'nil equals false and 0' do
result = compile_and_run(<<~BE)
def main(): Int
if nil == false and nil == 0
return 0
end
return 1
end
BE
expect(result.exit_code).to eq(0)
end
it 'nil can be returned from functions' do
result = compile_and_run(<<~BE)
def maybe_value(x: Int): Int
if x > 0
return x
end
return nil
end
def main(): Int
v = maybe_value(0)
if v
return 1
end
return 0
end
BE
expect(result.exit_code).to eq(0)
end
end
Compatibility Notes
nil,false, and0are all the same value at runtime- This is by design for C FFI compatibility (NULL == 0)
- Type system can distinguish them at compile time if needed
- Existing code using
0as “no value” continues to work
Related Future Work
- Optional chaining:
user?.profile?.name - Nil coalescing:
value ?? default - Guard clauses:
return if x == nil - Postfix guards with nil:
result? or return nil
Created: January 26, 2026
Decision: Both nil + Option types (hybrid approach)