24. Examples
Complete programs, each written against the rules of this document. They are the shortest programs that use each part of the language, not the shortest programs possible. Every program is in a package with this manifest and is built and run as shown:
[package]
name = "examples"luce build main.lucb -o main # one file, native target of the host
./main
luce test # every `test` declaration in the package
luce build --lib # a library plus its C header24.1 Arguments and output#
pub func main(arguments: str[]) -> i32:
if arguments.length < 2:
print("usage: greet NAME")
return 1
print(f"hello, {arguments[1]}")
return 024.2 A struct with defaults, a custom initialiser, and a static constructor#
pub let out_of_range: ErrorCode = ErrorCode.package(1)
pub struct Style:
pub let width: f64 = 1.0
pub let dashed: bool = false
pub let color: u32 = 0xFFFFFF
pub struct Percentage:
let value: f64
pub func init(value: f64) -> !:
if value < 0.0 or value > 100.0:
error(out_of_range, "percentage must be 0 through 100")
self.value = value
pub static func half() -> Percentage:
return Percentage(value = 50.0) catch failure: trap("50 is in range")
pub func main(arguments: str[]) -> i32!:
let thin = Style(width = 0.5) # dashed and color defaulted
let opacity = try Percentage(75.0)
let half = Percentage.half()
print(f"{thin.width} {opacity.value} {half.value}")
return 024.3 A ring buffer#
A struct that owns a span, allocated from the allocator that was current when it was made and released through the same one.
import memory
from memory import Allocator
pub let full: ErrorCode = ErrorCode.package(2)
pub struct Ring:
var items: u32[]
var head: usize
var count: usize
var allocator: Allocator
pub static func create(capacity: usize) -> Ring!:
return Ring(items = try new u32[capacity], head = 0, count = 0, allocator = memory.allocator)
pub func push(value: u32) -> !:
if self.count == self.items.length:
error(full, "ring buffer is full")
self.items[(self.head + self.count) % self.items.length] = value
self.count += 1
pub func pop() -> u32?:
if self.count == 0: return none
let value = self.items[self.head]
self.head = (self.head + 1) % self.items.length
self.count -= 1
return value
pub func destroy():
free(self.items) in self.allocator
pub func main(arguments: str[]) -> i32!:
var ring = try Ring.create(4)
defer ring.destroy()
for value: u32 in 1..=4:
try ring.push(value)
while let value = ring.pop():
print(f"{value}")
return 024.4 An arena allocator#
A type that implements Allocator, remembers its parent, and is used through with.
import c
import luce_std.files
import memory
from memory import Allocator
pub struct Arena: Allocator:
var parent: Allocator
var block: u8[]
var used: usize
pub static func over(parent: Allocator, capacity: usize) -> Arena!:
with parent:
return Arena(parent = parent, block = try memory.allocate(capacity, 16), used = 0)
pub func allocate(size: usize, alignment: usize) -> u8[]?:
let base = (usize)self.block.data
let start = ((base + self.used + alignment - 1) & ~(alignment - 1)) - base
let end = start +? size else return none
if end > self.block.length: return none
self.used = end
return self.block[start..<end]
pub func resize(block: u8[], size: usize) -> bool:
return size <= block.length
pub func release(block: u8[]):
return # an arena releases everything at once
pub func reset():
self.used = 0
pub func destroy():
free(self.block) in self.parent
func words(text: const u8[]) -> usize:
var count: usize = 0
var inside = false
for byte in text:
let space = byte == ' ' or byte == '\n'
if not space and not inside: count += 1
inside = not space
return count
pub func main(arguments: c.str[]) -> i32!:
var arena = try Arena.over(memory.allocator, 1 << 20)
defer arena.destroy()
with arena:
let text = try files.read(arguments[1]) # the bytes in the arena, freed by destroy
defer text.release()
print(f"{words(text.value)}")
return 024.5 An intrusive list#
Pointers, nullable pointers, memory.offset_of, and the two casts C uses for the same structure.
import memory
struct Link:
var next: Link*?
struct Task:
var name: str
var priority: u32
var link: Link
func task_of(link: Link*) -> Task*:
return (Task*)((u8*)link - memory.offset_of(Task, link))
func push(head: Link*?*, task: Task*):
task.link.next = *head
*head = &task.link
func highest(head: Link*?) -> Task*?:
var best: Task*? = none
var current = head
while let link = current:
let task = task_of(link)
if let leader = best:
if task.priority > leader.priority: best = task
else:
best = task
current = link.next
return best
pub func main(arguments: str[]) -> i32:
var tasks = [Task(name = "build", priority = 2, link = Link(next = none)),
Task(name = "test", priority = 5, link = Link(next = none)),
Task(name = "ship", priority = 1, link = Link(next = none))]
var head: Link*? = none
for task in &tasks:
push(&head, task)
if let task = highest(head):
print(task.name) # test
return 024.6 A tagged union#
An enum with payloads, heap-allocated cases with errdefer, and an exhaustive match expression.
enum Expr:
number(value: f64)
add(left: Expr*, right: Expr*)
multiply(left: Expr*, right: Expr*)
negate(operand: Expr*)
func evaluate(expression: const Expr*) -> f64:
return match *expression:
.number(value) => value
.add(left, right) => evaluate(left) + evaluate(right)
.multiply(left, right) => evaluate(left) * evaluate(right)
.negate(operand) => -evaluate(operand)
func release(expression: Expr*):
match *expression:
.number(_): return
.add(left, right), .multiply(left, right):
release(left)
release(right)
.negate(operand): release(operand)
free(expression)
pub func main(arguments: str[]) -> i32!:
let two = try new Expr.number(value = 2.0)
errdefer release(two)
let three = try new Expr.number(value = 3.0)
errdefer release(three)
let sum = try new Expr.add(left = two, right = three)
errdefer free(sum)
let negated = try new Expr.negate(operand = sum)
defer release(negated)
print(f"{evaluate(negated)}") # -5.0
return 024.7 Flags#
enum Access as u32:
empty = 0
read = 1
write = 2
execute = 4
func describe(mode: Access) -> str:
return match mode:
.empty => "no access"
.read => "read-only"
_ if (mode & Access.write) != Access.empty => "writable"
_ => "other"
pub func main(arguments: str[]) -> i32:
let mode = Access.read | Access.write
print(f"{describe(mode)} {(u32)mode}") # writable 3
return 024.8 Calling C#
A binding written by hand: an opaque handle, a nullable result, C text, and cleanup with defer.
import c
extern type Window
extern func SDL_Init(flags: u32) -> i32
extern func SDL_GetError() -> c.str
extern func SDL_CreateWindow(title: c.str, x: i32, y: i32, w: i32, h: i32, flags: u32) -> Window?
extern func SDL_DestroyWindow(window: Window)
extern blocking func SDL_Delay(ms: u32)
extern func SDL_Quit()
pub let sdl_failed: ErrorCode = ErrorCode.package(3)
func last_error() -> str:
return str(SDL_GetError()) catch failure:
recover "SDL reported invalid text"
pub func main(arguments: str[]) -> i32!:
if SDL_Init(0x20) != 0:
error(sdl_failed, last_error())
defer SDL_Quit()
let window = SDL_CreateWindow("Luce Base", 100, 100, 800, 600, 0) else error(sdl_failed, last_error())
defer SDL_DestroyWindow(window)
SDL_Delay(2000)
return 024.9 Exporting to C#
A Base function with a C-representable signature, and the header the compiler writes for it.
export func checksum(data: const u8[]) -> u32:
var sum: u32 = 0
for byte in data:
sum = sum *% 31 +% byte # byte widens to u32 implicitly
return sum#include <stdint.h>
#include <stddef.h>
uint32_t checksum(const uint8_t *data, size_t data_length);24.10 A spinlock and a counter#
import thread
struct SpinLock:
var locked: @bool
func acquire():
while self.locked.swap(true, .acquire):
thread.pause()
func release():
self.locked.store(false, .release)
struct Shared:
var lock: SpinLock
var total: u64
func bump(context: void*):
let shared = (Shared*)context
for _ in 0..<100_000:
shared.lock.acquire()
shared.total += 1
shared.lock.release()
pub func main(arguments: str[]) -> i32!:
var shared: Shared
var handles: thread.Handle[4]
for index in 0..<4:
handles[index] = try thread.spawn(bump, &shared)
for handle in handles:
try handle.join()
print(f"{shared.total}") # 400000
return 024.11 A generic over a span#
from luce import Comparable
func largest[T: Comparable](values: const T[]) -> T?:
if values.length == 0: return none
var best = values[0]
for value in values:
if value.compare(best) > 0: best = value
return best
pub func main(arguments: str[]) -> i32:
let numbers = [3, 9, 4]
let top = largest(numbers) else trap("nonempty")
print(f"{top}") # 9
return 024.12 Text without allocation#
Formatted output into a caller's buffer, a str that views it, and a fmt parameter that forwards a formatted string.
import io
import luce
from io import Location
import strings
func describe(bytes: usize, buffer: u8[]) -> str!:
if bytes >= 1 << 20:
return try strings.format(buffer, f"{bytes >> 20} MiB")
if bytes >= 1 << 10:
return try strings.format(buffer, f"{bytes >> 10} KiB")
return try strings.format(buffer, f"{bytes} B")
func log(message: fmt, at: Location = luce.location):
_ = io.stderr().write(f"{at.file}:{at.line}: {message}\n") catch failure: recover 0
pub func main(arguments: str[]) -> i32!:
var buffer: u8[32]
let size = try describe(3_145_728, buffer)
print(size) # 3 MiB
log(f"described {size}")
return 024.13 A string builder#
A Writer over memory it owns, growing with resize where the allocator allows and reallocating otherwise.
import memory
from io import Writer
from memory import Allocator
pub struct Builder: Writer:
var bytes: u8[]
var length: usize
var allocator: Allocator
pub static func create(capacity: usize) -> Builder!:
return Builder(bytes = try new u8[capacity] ---, length = 0, allocator = memory.allocator)
pub func write(data: const u8[]) -> usize!:
let needed = self.length +? data.length else error(memory.exhausted, "builder overflow")
if needed > self.bytes.length:
let grown = try new u8[needed * 2] --- in self.allocator
memory.copy(grown, self.bytes, self.length)
free(self.bytes) in self.allocator
self.bytes = grown
memory.copy(self.bytes[self.length..], data, data.length)
self.length = needed
return data.length
pub func text() -> str:
return (str)self.bytes[..<self.length]
pub func destroy():
free(self.bytes) in self.allocator
pub func main(arguments: str[]) -> i32!:
var builder = try Builder.create(16)
defer builder.destroy()
for index in 0..<3:
try builder.write(f"line {index}\n")
print(builder.text())
return 024.14 A hash map#
A generic open-addressing map keyed by any hashable, equatable type.
import memory
from luce import Equatable, Hashable
from memory import Allocator
pub struct Map[K: Hashable & Equatable, V]:
var keys: K[]
var values: V[]
var used: bool[]
var count: usize
var allocator: Allocator
pub static func create(capacity: usize) -> Map[K, V]!:
return Map(keys = try new K[capacity] ---, values = try new V[capacity] ---,
used = try new bool[capacity], count = 0, allocator = memory.allocator)
func slot(key: K) -> usize:
var index = (usize)(key.hash() % self.keys.length)
while self.used[index] and not (self.keys[index] == key):
index = (index + 1) % self.keys.length
return index
pub func insert(key: K, value: V) -> !:
if self.count == self.used.length:
error(memory.exhausted, "map is full")
let index = self.slot(key)
if not self.used[index]:
self.used[index] = true
self.keys[index] = key
self.count += 1
self.values[index] = value
pub func find(key: K) -> V?:
let index = self.slot(key)
if not self.used[index]: return none
return self.values[index]
pub func destroy():
free(self.keys) in self.allocator
free(self.values) in self.allocator
free(self.used) in self.allocator
pub func main(arguments: str[]) -> i32!:
var ages = try Map[str, u32].create(16)
defer ages.destroy()
try ages.insert("ada", 36)
try ages.insert("alan", 41)
let age = ages.find("alan") else trap("inserted")
print(f"{age}") # 41
return 024.15 Error handling end to end#
A fallible function calling another, one catch that recovers, one that adds context through a caller's buffer, and main reporting.
import c
import luce_std.files
import strings
pub let missing_field: ErrorCode = ErrorCode.package(4)
struct Config:
var name: str
var threads: u32
func field(text: str, key: str) -> str?:
let bytes = text.bytes
var start: usize = 0
while start < bytes.length:
var end = start
while end < bytes.length and bytes[end] != '\n':
end += 1
let line = bytes[start..<end]
if line.length > key.length and (str)line[..<key.length] == key:
var value = line[key.length..]
while value.length > 0 and (value[0] == ' ' or value[0] == '='):
value = value[1..]
return (str)value
start = end + 1
return none
func number(text: str) -> u32!:
if text.length == 0:
error(missing_field, "empty number")
var value: u32 = 0
for byte in text.bytes:
if byte < '0' or byte > '9':
error(missing_field, "not a digit")
value = value * 10 + u32(byte - '0')
return value
func parse(text: str, scratch: u8[]) -> Config!:
let name = field(text, "name") else error(missing_field, "name")
let threads = field(text, "threads") else error(missing_field, "threads")
let count = number(threads) catch failure:
error(failure.code, try strings.format(scratch, f"threads: {failure.message}"))
return Config(name = name, threads = count)
func load(path: c.str, scratch: u8[]) -> Config!:
let bytes = files.read(path) catch failure:
if failure.code == files.missing:
return try parse("name = default\nthreads = 4", scratch)
error(failure.code, failure.message)
defer bytes.release()
return try parse(try str(bytes.value), scratch)
pub func main(arguments: c.str[]) -> i32!:
var scratch: u8[256]
let config = try load(arguments[1], scratch)
print(f"{config.name} on {config.threads} threads")
return 0