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.