Skip to main content

05. Type mapping table

This note enumerates the closed translation table the bridge uses to map CLR types to Mochi types. Items whose entire signature falls inside the table are translated; items with any out-of-table type are skipped with a SkipReport.

Scalar types

CLR typeMochi typeNotes
System.Int32 (int)intWidened to int64 at the native boundary.
System.Int64 (long)intNative fit.
System.Int16 (short)intWidened to int64.
System.Byte (byte)intWidened to int64.
System.SByte (sbyte)intWidened to int64.
System.UInt16 (ushort)intWidened to int64.
System.UInt32 (uint)intWidened to int64; values >= 2^31 positive in Mochi.
System.UInt64 (ulong)intTranslated as int64; values >= 2^63 panic the shim.
System.Single (float)doubleWidened to float64 at the boundary.
System.Double (double)doubleNative fit.
System.Boolean (bool)boolPassed as int32 (0/1) across the native boundary.
System.Char (char)stringTranslated as a 1-codepoint Mochi string (Mochi has no char type).
voidunitFunctions returning void emit Mochi fun(...): unit.
System.ObjectanyThe CLR base type. Passed as an opaque GCHandle.

String types

CLR typeMochi typeNotes
System.StringstringOwned. Marshalled as UTF-8 byte* + int length via MochiMarshal.StringToCoTaskMem.
System.Text.StringBuilder(refused)Mutable string type; no Mochi analogue.
System.ReadOnlySpan<char>(refused, v1)Managed reference type; cannot cross the native boundary.
System.Span<T>(refused)Managed reference type.
System.Memory<T>(refused)Same.

Collection types

CLR typeMochi typeNotes
System.Collections.Generic.List<T> where T in tablelist<T>Marshalled as a MochiSlice (pointer + length + capacity).
T[] (array) where T in tablelist<T>Fixed-size CLR array. Translated as a MochiSlice with capacity = length.
System.Collections.Generic.IEnumerable<T> where T in tablelist<T>Materialised into a List<T> before marshalling.
System.Collections.Generic.Dictionary<K, V> where K = string and V in tablemap<string, V>Marshalled as a MochiMap opaque handle.
System.Collections.Generic.Dictionary<K, V> where K is integer type and V in tablemap<int, V>Same.
System.Collections.Generic.HashSet<T> where T in tableset<T>Marshalled as a MochiSet opaque handle.
System.Collections.Generic.IList<T>list<T> (materialised)Materialised into a List<T> before marshalling.
System.Collections.Generic.IReadOnlyList<T>list<T> (materialised)Same.
System.Linq.IQueryable<T>(refused)LINQ queryable; requires runtime query compilation.

Nullable, Task, and special types

CLR typeMochi typeNotes
T? where T is a value type in tableT|nilNullable<T> struct. Null becomes nil; non-null becomes the value.
T? where T is a reference type in tableT|nilReference nullable. Null becomes nil.
System.Threading.Tasks.Task<T> where T in table(synchronous dispatch)See 08-async-bridge. The return type becomes T in Mochi; the shim calls .GetAwaiter().GetResult().
System.Threading.Tasks.Taskunit (async)A Task without a result is treated as fun(): unit.
System.Threading.Tasks.ValueTask<T>(same as Task)Treated identically to Task<T> at the shim boundary.
System.Threading.CancellationToken(refused, v1)No Mochi cancellation primitive in v1.
System.GuidstringMarshalled as a UUID string (36-character xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format).
System.DateTimeintMarshalled as Unix timestamp (milliseconds since epoch) as int64.
System.DateTimeOffsetintSame as DateTime.
System.TimeSpanintMarshalled as total milliseconds as int64.
System.UristringMarshalled as the string form of the URI.

Struct and record types

A CLR struct with all-fields-in-table and default layout (no [StructLayout(LayoutKind.Explicit)]) translates to a Mochi record:

public struct Point {
public double X;
public double Y;
}

becomes:

record Point {
X: double,
Y: double,
}

A struct with any out-of-table field type is refused. A struct with [StructLayout(LayoutKind.Explicit)] or unsafe fields is refused.

A C# record type (the record class or record struct introduced in C# 9) is treated identically to a struct or class: if all property types are in-table, it translates; otherwise it is refused.

Class and interface types

A class with no constructor parameters that has in-table public methods and no abstract required-override methods translates to an opaque handle:

public class HttpClient {
public async Task<string> GetStringAsync(string requestUri) { ... }
}

becomes:

extern type HttpClient
extern fn http_client_new(): HttpClient from dotnet "System.Net.Http.HttpClient..ctor"
extern fn http_client_get_string_async(client: HttpClient, uri: string): string from dotnet "System.Net.Http.HttpClient.GetStringAsync"

The HttpClient is held as a GCHandle.Alloc(instance, GCHandleType.Normal) on the CLR side; the Mochi side receives the GCHandle.ToIntPtr() value and treats it as an opaque nint handle. The shim provides a _free method that calls GCHandle.Free.

An interface type translates as an opaque handle. The bridge generates shim methods for each interface method, dispatching via the interface method on the held object reference.

Abstract classes and abstract methods are refused: instantiation requires a concrete implementation that the bridge cannot synthesise.

Enum types

CLR patternMochi translationNotes
enum E { A, B, C } (no values)type E = A | B | CCompact integer encoding.
enum E { A = 1, B = 2, C = 4 } (explicit values)type E = A | B | CValues passed as int at the boundary.
[Flags] enum E { A = 1, B = 2, C = 4 }int with named constantsFlags enums translate to int; the bridge emits named constants.

Generic types and [dotnet.monomorphise]

A generic method or class with unconcretised type parameters is refused at default ingest:

SKIPPED: Newtonsoft.Json.JsonConvert.DeserializeObject`1[T]
Reason: SkipUnconcretisedGeneric
Detail: generic parameter T has no explicit monomorphisation declared
Override: add { item = "Newtonsoft.Json.JsonConvert.DeserializeObject", T = "MyType" } to [dotnet.monomorphise]

The user enables import by enumerating concretisations:

[dotnet]
monomorphise = [
{ item = "Newtonsoft.Json.JsonConvert.DeserializeObject", T = "MyType" },
{ item = "System.Collections.Generic.List`1.Add", T = "string" },
]

For each enumeration, the bridge generates a monomorphised shim method:

extern fn json_convert_deserialize_my_type(json: string): MyType from dotnet "Newtonsoft.Json.JsonConvert.DeserializeObject`1[MyType]"
extern fn list_string_add(list: any, item: string) from dotnet "System.Collections.Generic.List`1[System.String].Add"

Unlike Rust's monomorphisation (which is a compile-time template expansion), CLR generic methods are JIT-compiled per closed instantiation at runtime. The mochi-dotnet-meta tool emits the open generic signature; the shim generates the closed instantiation signature using the CLR's type name format: Foo`1[FullTypeName].

Refusal reasons

A SkipReport entry records:

type SkipReport struct {
ItemPath string // e.g., "System.IO.StreamReader.ReadAsync"
Reason SkipReason
Detail string
}

type SkipReason int
const (
SkipPointerType SkipReason = iota // T* (unsafe pointer)
SkipByRef // ref T or out T parameter
SkipUnconcretisedGeneric // unconcretised generic type parameter
SkipSpanType // Span<T> or ReadOnlySpan<T>
SkipMemoryType // Memory<T> or ReadOnlyMemory<T>
SkipCancellationToken // System.Threading.CancellationToken
SkipAbstractClass // abstract class or abstract method
SkipDelegate // delegate type (no Mochi analogue)
SkipEventInfo // event (add/remove handlers; no Mochi analogue)
SkipComImport // [ComImport] attribute (COM interop type)
SkipUnsafeMethod // method marked unsafe
SkipInternalVisibility // internal or private method
SkipObsolete // [Obsolete(error: true)] method
SkipMulticastDelegate // multicast delegate invocation list
SkipDynamicType // dynamic CLR type (late-bound)
SkipQueryable // IQueryable<T> requires LINQ provider
SkipFunctionPointer // delegate* managed/unmanaged
)

The bridge emits the SkipReport list to <workdir>/dotnet_shim/<pkg>/SKIPPED.txt:

SKIPPED: System.IO.Stream.ReadAsync
Reason: SkipCancellationToken
Detail: parameter "cancellationToken" of type System.Threading.CancellationToken cannot be expressed in Mochi v1
Override: write a custom extern fn that omits the cancellationToken parameter

SKIPPED: System.Collections.Generic.List`1.Sort
Reason: SkipDelegate
Detail: parameter "comparison" of type System.Comparison`1[T] is a delegate type
Override: write a custom extern fn that uses a specific comparison function

Cross-references