Appendix A: Goose for C, Odin and Rust Programmers
This appendix is a phrasebook. For each language, it lists the things you reach for out of habit and what Goose offers instead, with the chapter that explains it. The tables are about habits, not exact equivalence. Several rows translate a mechanism into a different design rather than into different syntax.
#From CA.1
| In C | In Goose | Chapter |
|---|---|---|
malloc and free |
a value of variable or resizable size, owned by a scope; freed when the scope ends | 6 |
a buffer that grows with realloc |
T[>..], or T[>..<] if it must also shrink |
7 |
int a[8] |
i32[8] |
7 |
| a pointer and a length | a slice, T[:] |
8 |
const T * |
const T&, or const T[:] |
8 |
T * that may be NULL |
T?, narrowed by if or guard |
11 |
char * strings |
u8[:] views, u8[] stored, u8[>..] built |
9 |
sprintf, snprintf |
str(…), format(out, …), and print |
9 |
| a struct with padding | a packed struct; pad where the layout must match C |
10 |
| a tagged union | enum with payloads, and match |
12 |
| a function pointer table | case functions, or an enum with match |
12 |
qsort with a comparator |
sort(xs) { a, b => … } |
16 |
a linked list or tree of malloced nodes |
nodes in one array, joined by T&<u32> links |
13 |
| a free list | a reusable pool |
14 |
return -1 and errno |
a trailing bool, an optional, or return … from |
15 |
setjmp and longjmp |
return … from, or the exception module |
15 |
assert |
assert, which is checked in release builds too |
15 |
pthread_create and mutexes |
thread_fn, thread_spawn, and typed queues; no shared memory |
20 |
fwrite of a struct |
to_bytes, and from_bytes to load it with checks |
21 |
| header files and separate compilation | import, and whole-program compilation |
19 |
-O2 -fno-bounds-checks |
bounds checks removed by proof; checkfree to insist |
23 |
The biggest change from C is ownership. In C, any code holding a pointer may free what it points at, and nothing records who should. In Goose, storage belongs to the scope that made it, and references into it are checked against that scope.
#From OdinA.2
Odin and Goose are both data-oriented languages for people who like C, so much of Odin carries over. The differences are mostly about memory.
| In Odin | In Goose | Chapter |
|---|---|---|
context.allocator, context.temp_allocator |
data stacks, chosen by the compiler; no allocator to pass or set | 6 |
[dynamic]T |
T[>..] or T[>..<] |
7 |
[N]T |
T[N] |
7 |
[]T |
T[:] |
8 |
^T |
T&, which can never dangle, or T? if it may be null |
8, 11 |
string |
u8[:]; u8[] when stored; u8[>..] when built |
9 |
strings.Builder |
u8[>..], written with format |
9 |
map[K]V |
dictionary<K, V> from the library |
18 |
Maybe(T) |
T? for a reference; a trailing bool for a value |
11 |
union and type switch |
enum with payloads, and match |
12 |
#soa |
parallel arrays by hand, sharing an index type | 23 |
explicit overload sets, proc{a, b} |
implicit overloading by parameter types | 5 |
x->f(), for structs holding procedures |
x.f(a), which calls f(x, a) for any function |
5 |
or_return |
return … from, or a trailing bool |
15 |
defer |
defer |
4 |
cast(T)x, transmute(T)x |
x as T, and x as! T when losing bits is intended |
3 |
% (truncating) and %% (floored) |
%, Euclidean on integers, and rem for the truncating form |
3 |
foreign import and foreign blocks |
extern fn, and extern "lib" { … } |
22 |
| packages as directories | files with namespace, and packages declared by the root file |
19 |
core:thread, core:sync |
workers and typed queues | 20 |
Odin trusts you with its allocators. Goose takes that choice away, and in exchange makes every reference safe. If you wrote Odin with arenas and temporary allocators, Goose's data stacks will feel familiar: they are an arena per scope, managed by the compiler.
#From RustA.3
Rust and Goose both promise memory safety without a garbage collector, by very different routes. Rust checks borrows of heap data with lifetimes. Goose has no heap, and checks references against the scopes that own the storage, inferring the roots that Rust would ask you to write as lifetime parameters.
| In Rust | In Goose | Chapter |
|---|---|---|
Box<T> |
nothing: a value is stored where it is declared | 6 |
Vec<T> |
T[>..] or T[>..<] |
7 |
&[T], &mut [T] |
const T[:], T[:] |
8 |
&T, &mut T |
const T&, T& |
8 |
lifetime parameters, 'a |
inferred roots | 8 |
String, &str |
u8[>..] or u8[], and u8[:], all bytes rather than checked UTF-8 |
9 |
format!, write! |
str, format |
9 |
Option<&T> |
T?, which is always a reference |
11 |
Option<T> for a value |
a trailing bool, or a default value |
11 |
enum with data, and match |
enum with payloads, and match |
12 |
Rc<RefCell<T>>, Weak |
a reusable pool, and links into it |
14 |
Result<T, E> and ? |
a trailing bool or error value, return … from, exception |
15 |
panic! |
abort, which cannot be caught |
15 |
traits and impl blocks |
overloads, generics without bounds, and method-call syntax | 5, 16 |
| closures | trailing blocks and nested functions, passed down but never stored | 16 |
Box<dyn Trait> |
an enum of the possible kinds |
12 |
std::thread, mpsc channels |
thread_spawn, and one queue per type |
20 |
Send, Sync |
flat values; nothing is shared | 20 |
serde |
to_bytes and from_bytes |
21 |
extern "C", #[no_mangle] |
extern fn, export fn |
22 |
get_unchecked |
an index type, or an assert that lets the check be removed |
23 |
| Cargo and crates | packages declared in the root file, fetched by goose |
19 |
The Rust habit that transfers least well is building structures of owned
nodes. A tree of Boxes becomes nodes in one array; a graph of Rcs
becomes a pool and links; a Vec<String> becomes a u8[][>..], one block
with every string inline.