Skip to main content

05. Type mapping table

This note documents the closed translation from Go types to Mochi types. The table is the same shape MEP-73's §05 documents for Rust, adapted for Go's type system.

Scalar types

Go typeMochi typeNotes
boolboolDirect passthrough.
intintMochi int is 64-bit on all targets; Go int is platform-width. The wrapper inserts a runtime check when Go int is 32-bit (rare: only on 32-bit linux / wasm-js).
int8, int16, int32intSign-extend on the C side.
int64intDirect passthrough (both are 64-bit).
uint, uint8, uint16, uint32intThe wrapper guards positive-range at the boundary; out-of-range values raise a runtime error.
uint64intThe wrapper rejects values > int64.MaxValue at the boundary.
uintptrintSame as uint64.
float32floatWidening cast.
float64floatDirect passthrough.
byte (= uint8)int(with positive-range guard)
rune (= int32)int
complex64, complex128refusedMochi has no complex type. The user can hand-write an extern fn that takes (real, imag) pairs.

Strings and byte slices

Go typeMochi typeNotes
stringstringAt the cgo boundary, Go's string is copied to a C char* via C.CString. The wrapper inserts the matching C.free call inside a defer block. Free symbol: mochi_go_<module>_string_free(*const c_char).
[]bytebytesOwned (ptr, len, cap) triple. The wrapper inserts a _free symbol that calls runtime.KeepAlive and lets Go GC handle reclamation after the Mochi side calls free.

The string round-trip cost is a C.CString allocation per call (the Go side cannot pass its internal string directly because the underlying buffer is GC-tracked and can move). For hot loops, the user can opt into a string builder pattern via the batched wrapper variant.

Collection types

Go typeMochi typeNotes
[]T (slice of in-table T)list<T>(ptr, len, cap) triple. The wrapper provides element-wise getter/setter symbols.
[N]T (array of in-table T)list<T>Fixed-size on the Go side; the wrapper allocates a slice and copies.
map[K]V (K is string or integer; V is in-table)map<K, V>Opaque *C.MochiMap handle. Symbols: _get, _set, _delete, _iter_begin, _iter_next, _iter_end, _free.
map[K]V (K is a struct or interface)refusedMochi's map keys are string or integer; mapping struct keys would require hashing on the C side which the wrapper does not implement.

Go's map is the trickiest case. Go maps cannot be passed across the cgo boundary because the underlying hash table is GC-tracked and the bucket layout is internal. The wrapper exposes a per-handle iterator with _iter_* symbols; the Mochi side bridges this to Mochi's for k, v in m { ... } loop via the iterator protocol. Iteration order is non-deterministic on the Go side (Go intentionally randomises map iteration); the wrapper documents this.

Pointer types

Go typeMochi typeNotes
*T (T is a named struct)T? (nullable handle)Opaque cgo handle. The wrapper exposes constructor / accessor symbols per exported field.
*T (T is a named primitive like *int)int?Auto-dereferenced on the boundary; nil maps to Mochi nil.
*T (T is unexported)refusedThe Mochi side cannot construct or inspect an unexported type.
**T (pointer-to-pointer)refusedMochi has no pointer arithmetic; double-pointer semantics don't translate.

Struct types

A Go struct with all-exported fields, all of which are in-table, becomes a Mochi record:

type User struct {
ID int64
Name string
Email string
}
record User {
ID: int,
Name: string,
Email: string,
}

A struct with mixed-export fields (some uppercase, some lowercase) projects the exported subset:

type Internal struct {
Public string
private int
}
record Internal {
Public: string,
}

A SkipReport entry records the dropped private field. The wrapper exposes only the public accessor.

A struct embedding another struct or interface promotes the embedded type's exported fields and methods. The bridge resolves promotions at ingest time.

Interface types

A Go interface becomes a Mochi extern type with per-method extern fn declarations:

type Reader interface {
Read(p []byte) (n int, err error)
}
extern type Reader
extern fn (r: Reader) read(p: bytes): Result<int> from go "io.Reader.Read"

The Mochi side gets an opaque handle; method calls go through cgo. Mochi cannot itself implement a Go interface (the implementation would have to live on the Go side); a future MEP could add this via callback handles, but MEP-74 v1 does not.

Channel types

A Go chan T (bidirectional) becomes a Mochi stream<T>:

func Tick(d time.Duration) <-chan time.Time
extern fn tick(d: int): stream<int> from go "time.Tick"

The wrapper allocates a Go channel of buffer size [go.goroutine-bridge.default-buffer], registers it as a cgo handle, and exposes _send, _recv, _close symbols. The Mochi side consumes the stream via the normal stream-iterator protocol.

Direction-restricted channels (<-chan T, chan<- T) project the appropriate subset of operations. A receive-only channel maps to a Mochi stream<T> with the send operation disabled.

Function types

A Go func value (taken as a parameter, returned from a function, or stored in a field) becomes a Mochi callback handle:

func Walk(root string, fn func(path string) error) error
extern fn walk(root: string, fn: fun(path: string) -> Result<unit>): Result<unit> from go "path/filepath.Walk"

The wrapper registers the Mochi callback as a cgo handle, calls into Go, and dispatches each invocation back to Mochi via the _call cgo export. The cost is two cgo crossings per callback invocation (Mochi → Go → callback → Mochi → Go → return).

The error interface

Go's error is an interface with one method (Error() string). MEP-74 treats it as a built-in sum type rather than as a generic interface:

func Open(name string) (*File, error)
extern fn open(name: string): Result<File> from go "os.Open"

The Mochi Result<T> desugar uses the same try / catch lowering MEP-73 introduced for Result<T, E> in Rust. The error value's Error() string call result is the carried message.

Tuple returns

A Go function returning multiple values returns a tuple to Mochi:

func Split(s string, sep string) []string
func Cut(s string, sep string) (before, after string, found bool)
extern fn split(s: string, sep: string): list<string> from go "strings.Split"
extern fn cut(s: string, sep: string): tuple<string, string, bool> from go "strings.Cut"

The last-position error is special-cased: (T, error) becomes Result<T> rather than tuple<T, Result<unit>>. The (T1, T2, error) becomes Result<tuple<T1, T2>>. The desugar is documented per-fn in the emitted shim.

Variadic parameters

A Go ...T variadic parameter becomes Mochi varargs<T>:

func Printf(format string, args ...any)

The any case is refused by default; with [go.monomorphise] declarations, the user can bind Printf at concrete types.

Generic items

Go 1.18+ generics are refused by default; opt-in via [go.monomorphise]. The bridge synthesises one wrapper per declared instantiation:

[go.monomorphise]
items = [
{ item = "encoding/json.Unmarshal", T = "MyStruct" },
{ item = "slices.Sort", T = "int64" },
{ item = "slices.Sort", T = "string" },
]

The wrapper emits mochi_go_<module>_<fn>_<T> symbols per instantiation. The Mochi shim file declares each as a separate extern fn.

Refusal cases

Items are skipped with a SkipReport for the following reasons:

ReasonExample
unexported_in_positionA function returning an unexported type.
internal_pathItem lives under <module>/internal/.
generic_without_monomorphiseGeneric item not listed in [go.monomorphise].
unsafe_pointerItem involves unsafe.Pointer.
reflect_valueItem involves reflect.Value or reflect.Type.
cgo_handleItem involves cgo.Handle directly (without going through the bridge's own handle pool).
interface_with_complex_methodInterface method returns a type outside the table.
chan_of_struct_with_unexportedChannel element type fails the struct projection.
map_key_not_basicMap key is a struct or interface.
requires_cgo_capabilityModule declares import "C" and the user has not opted in.
build_tag_excludedItem is behind a build tag not in [go.build-tags].

Each SkipReport carries the qualified item name and the reason; the user sees them as warnings during mochi pkg lock.

Type round-trip table

Boundary directionCostNotes
Mochi int → Go int640 (passthrough)Both are 64-bit on 64-bit hosts.
Mochi string → Go string~50ns + allocC.CString then C.GoString.
Mochi list<int> → Go []int64O(n) copySlice header passed; backing memory copied.
Mochi bytes → Go []byteO(n) copySame as list.
Mochi callback → Go func~150ns/invocationHandle registration + dispatch.
Go chan T → Mochi stream<T>~200ns/elementcgo crossing per element.
Go error → Mochi Result<T>~100ns on error pathCalls .Error() string.

Cross-references