Appendix B: Limitations
Goose gets its safety and its speed by giving things up. Most of the limitations below are the price of the memory model, and are permanent. A few are omissions of the current version. This appendix collects them in one place, so you can judge whether a program suits the language before you start.
#Limits of the designB.1
You decide where data lives. Every growable value belongs to a scope,
and you choose which. Usually it is the function that builds the value,
and there is nothing to decide. When it is not, the data goes into a
reusable pool (Chapter 14) or is owned further up and passed down.
Recursion cannot hold growable data across a call. A recursive function may use scratch storage that ends before it recurses, but storage that must survive the call is owned outside the cycle and passed in (Chapter 17). Parsers and tree walks are written around this rule, as Chapter 25 shows.
Arrays of variable-size elements are sequential. u8[][>..] and arrays
of variable-mode enums can be walked but not indexed. To index one, keep a
separate array of references or offsets beside it (Chapter 13).
A fixed-mode enum cannot be pointed into, and a variable-mode one cannot be overwritten. Each container chooses its mode (Chapter 12).
No escaping closures, no function pointers, no dynamic dispatch. A function value is a compile-time entity. It can be passed down to a call but never stored or returned. Dispatch on a kind of value goes through an enum's tag. A plugin system, a table of callbacks registered at run time, or a virtual-function hierarchy has to be rewritten as an enum (Chapters 12 and 16).
Generics have no bounds. A generic function is checked when it is specialized, not when it is declared, so a mistake in a generic library function shows up as an error at a use (Chapter 16).
Whole-program compilation. There are no precompiled Goose libraries and no shared libraries of Goose code. Every build compiles the whole program from source (Chapter 19).
Threads share nothing. There are no locks or atomics, and no way for two threads to work on one array. Parallel work is divided by sending values through queues (Chapter 20).
A freed pool slot can be reused while something still refers to it. The reference then sees the slot's next value, of the same type. This cannot corrupt memory, but it is a logic error that the compiler does not catch (Chapter 14).
The checker is conservative. When a reference passes through a container or a function, the compiler may lose track of exactly which array it points into, and then assumes the worst. Chapter 24 shows a program that had to make a builder inside a loop for this reason. The workaround is usually to move a declaration, but the error can take some thought to understand.
Bytes, not characters. Strings are arrays of u8. The library treats
them as ASCII where it needs to know about letters, and UTF-8 text passes
through unchanged but is not decoded or checked (Chapter 9).
#Limits at the boundariesB.2
- Only fixed-size flat values, references, slices and builders cross to C. Variadic C functions cannot be declared at all (Chapter 22).
- An
export fncannot take a builder or have default arguments, and must be called from the thread that calledgoose_init. - The in-process runner cannot run threads. Use
goose buildfor programs that spawn workers. simd fnhas an effect only with clang on x86-64.- Data with links measured from a named global pool cannot be saved with
to_bytes(Chapter 21).
#Omissions of version 1B.3
These are not part of the design, but are missing from the current compiler and library:
- There is no move operation for resizable values, and a struct can have at most one resizable field.
- There is no labelled
break. Ablockwith a value, or a nested function withreturn, covers most uses. - Namespaces have no privacy, no nesting and no re-exports.
- The library has no subprocesses and no networking.
The Goose repository's documentation lists the current state of each of these, and some may have changed since this book was written.