Guide 46 of 53
Foreign Type Mappings
Exact integers, copied records, live objects, callbacks and async conversion.
On this page
An extern function header uses Zolo types; its body uses the selected native
language. The provider generates the conversion between them. The tables below
describe typed inline functions. Imports from native modules or files use the
native declaration's types, which can differ from the inline defaults.
Provider Names¶
| Language | Plugin | Imported provider |
|---|---|---|
| C# | dotnet |
csharp |
| Python | python |
python |
| JavaScript | node |
javascript |
| TypeScript | node |
typescript |
| Java | jvm |
java |
| Kotlin | jvm |
kotlin |
| C | native |
c |
| C++ | native |
cpp |
| Rust | rust |
rust |
| Go | go |
go |
Providers are named plugin imports and may have aliases. Native code still uses its own imports, includes, literals and method names.
Scalars¶
Zolo int is a signed 64-bit integer. It does not become the native language's
default integer type everywhere. For a native 32-bit parameter, use i32.
| Provider | Zolo int / i64 |
Zolo float / f64 |
Zolo str |
Zolo bool |
|---|---|---|---|---|
| C# | long |
double |
string |
bool |
| Python | int |
float |
str |
bool |
| JavaScript | bigint |
number |
string |
boolean |
| TypeScript | bigint |
number |
string |
boolean |
| Java | long |
double |
String |
boolean |
| Kotlin | Long |
Double |
String |
Boolean |
| C | int64_t |
double |
const char * |
bool |
| C++ | int64_t |
double |
std::string |
bool |
| Rust | i64 |
f64 |
String |
bool |
| Go | int64 |
float64 |
string |
bool |
Kotlin integer literals must match the generated Long parameter:
use plugin jvm::{kotlin}
extern kotlin fn next(value: int) -> int {
return value + 1L
}
print(next(41))Output:
42Java uses 1L for a long literal. JavaScript and TypeScript use 1n for a
bigint literal. An imported JS/TS number parameter also accepts a Zolo
integer when it can be represented exactly; its result is a Zolo float.
An explicit inline int signature keeps BigInt transport.
Narrow integers and floating-point types use the corresponding native shapes
where supported. For example, i32 is C# int, Java int, Kotlin Int, C/C++
int32_t, Rust i32 and Go int32. f32 is their native 32-bit float.
Python integers remain Python int; JS/TS narrow integers use number.
Java and Kotlin do not provide inline unsigned integer mappings. Overflow and
invalid numeric conversions report errors; an unsigned native type does not
give Zolo int a larger positive range. Python's unbounded integers and Java's
BigInteger must fit the declared Zolo integer range when returned as int.
A nil return generates void in C#, Java and C/C++, Unit in Kotlin,
() in Rust and no return value in Go. Python returns None; JavaScript and
TypeScript unit results become Zolo nil.
Reflected C# decimal, Guid, DateTime and DateTimeOffset values use
string representations. Java char / Character also maps to str.
Strings and Bytes¶
| Provider | Zolo bytes |
String detail |
|---|---|---|
| C# | byte[] |
Managed Unicode string |
| Python | bytes |
Results also accept bytearray and memoryview |
| JavaScript / TypeScript | Uint8Array / Buffer |
Native string |
| Java | byte[] |
Managed Unicode string |
| Kotlin | ByteArray |
Managed Unicode string |
| C | zolo_bytes |
UTF-8 NUL-terminated string; embedded NUL is rejected |
| C++ | zolo_bytes |
std::string preserves embedded NUL |
| Rust | Vec<u8> |
Owned UTF-8 String; embedded NUL is preserved |
| Go | []byte |
UTF-8 string; embedded NUL is preserved |
A C string parameter is a pointer, so it does not provide C++ string operators:
use plugin native::{c}
extern c fn greet(name: str) -> str {
return name;
}
print(greet("Zolo"))Output:
ZoloC arguments are borrowed during the invocation. Returned strings and byte
views are copied before control returns to Zolo. Keep returned pointers valid
until the adapter finishes copying them; never return a pointer to a local
stack buffer. Use bytes for binary data rather than a NUL-terminated string.
Nullable Values, Arrays and Maps¶
T? represents a value or nil. Map keys at these typed boundaries are
strings; declare the signature as #{str: T} and construct a Zolo map with
#{ key: value }.
| Provider | T? |
[T] |
#{str: T} |
|---|---|---|---|
| C# | Nullable value/reference type | T[] |
Dictionary<string, T> |
| Python | Value or None |
list; tuple results are copied too |
dict |
| JavaScript / TypeScript | Value or null |
Native array | Plain string-keyed record |
| Java | Boxed nullable primitive or nullable reference | Native array | Map<String, T>; primitive values are boxed |
| Kotlin | Native T? |
Array<T> |
Map<String, T> |
| C | Explicit zolo_value adapter |
[str] uses zolo_strings; other lists need an adapter |
Explicit zolo_value adapter |
| C++ | Explicit zolo_value adapter |
std::vector<T> |
std::map<std::string, T> |
| Rust | Option<T> |
Vec<T> |
std::collections::HashMap<String, T> |
| Go | *T |
[]T |
map[string]T |
For Java imports, native Collection<T> results also become Zolo arrays.
For JS/TS imports, native Map<string, T> signatures preserve native Map
arguments and copy returned entries; a plain inline record uses object property
access instead of Map.get. Imported Rust BTreeMap<String, T> and C++
std::unordered_map<std::string, T> also use typed string-keyed maps.
The C [str] adapter is a zolo_strings view with data of type
const char *const * and len of type size_t. Its elements are borrowed for
the call and reject embedded NUL. A returned view is cloned. Retaining the
incoming view after the call is unsupported. Arbitrary C lists, maps, records,
pointers and ownership conventions require a concrete zolo_value adapter.
Copied values do not share mutations with the original Zolo collection. Elements and fields must also have supported conversions. C++ collections of owning native objects or callbacks need explicit ownership adapters.
Records and Live Objects¶
Declared Zolo structs become copied DTOs in C#, Python, Java/Kotlin, JS/TS,
Rust and Go. C# and Java expose generated public fields; Kotlin exposes
properties, Python records support attribute access, and JS/TS records use
object properties. Rust receives generated public fields. Go generates
exported fields with zolo tags preserving the original Zolo field names.
For imported native Go structs, fields must be exported. The default map key
is the exact native field name; a zolo tag can supply another key:
use plugin go::{go}
extern go mod records {
type Person struct {
Name string
Age int64
}
func NameOf(person Person) string {
return person.Name
}
}
print(records.NameOf(#{ Name: "Ana", Age: 30 }))Output:
AnaImported Rust structs similarly need supported public fields. These copied
records may appear as Any in imported metadata because the native declaration
supplies their concrete layout. An inline Rust or Go parameter declared Any
has no native layout and is rejected; use a concrete type.
Imported classes in C#, Python, JS/TS, Java/Kotlin and C++ retain live native
identity. Their constructors use .new(...), and their exposed methods and
properties use ordinary Zolo access. Assigning such a value preserves its
identity. A copied DTO and a live native class are different boundary shapes;
keep the type expected by the native API. C++ live objects cannot cross
independently compiled module realms through an implicit ownership conversion.
Callbacks and Async¶
| Provider | fn(...) -> T callback |
Async boundary |
|---|---|---|
| C# | Func<...> / Action<...> or imported delegate |
Task<T> |
| Python | Callable proxy | Awaitable / coroutine |
| JavaScript / TypeScript | Native function | Promise<T> |
| Java | Supplier, Function, BiFunction and void-return equivalents |
CompletionStage<T> |
| Kotlin | Native function type | suspend functions; adapted completion stages |
| C | zolo_callback |
Provider invocation worker |
| C++ | std::function<...> |
Provider invocation worker |
| Rust | Call-scoped impl Fn(...) -> T |
Rust async export or provider invocation worker |
| Go | Call-scoped func(...) T |
Provider invocation worker |
Declare the Zolo binding async and use await on its result. C/C++/Go worker
execution does not supply a native Future type; Rust libraries requiring an
executor-specific reactor need an explicit wrapper. Java inline callbacks
with more than two parameters need a declared native functional interface.
C++ callable callbacks accept scalar/string parameters and scalar/string/nil
returns. C invokes callbacks through zolo_callback_call and releases returned
wire values through zolo_callback_release.
C/C++/Rust/Go callbacks are borrowed for an invocation. Do not store them for later or invoke them from an arbitrary native thread. Managed providers support retained callbacks through leases and the owning Zolo scheduler. Cancellation requests cooperation; it cannot forcibly stop arbitrary native or blocking work.
Imported Rust Result<T, E> and Go (T, error) signatures expose their
successful value as T and propagate failures through Zolo's error channel.
Go signatures with several unrelated return values need a wrapper returning
one supported value.
Explicit Adapters and API Boundaries¶
Use a small native wrapper when a library exposes raw pointers, open templates, variadic functions or an unsupported ownership convention. Convert its public signature to the scalar, collection, record or callback shapes above.
In C/C++, Any exposes a zolo_value for an explicit wire adapter.
For example, a C Any result can use zolo_string(...) to return a copied
string; use an ordinary -> str signature when a C string is sufficient.
Helpers live in the shared bridge header.
Manually allocated returned wire collections require compatible allocation
and ZOLO_OWNED; generated adapters handle copying and releasing their returns.
Managed dynamic boundaries map Any to C# dynamic, Java Object, Kotlin
Any? or JS/TS any; Python uses its native dynamic values. Copied dynamic
collections still follow the provider's key, numeric and cycle restrictions.
Rust and Go inline signatures require a concrete native layout instead.
The typed extern csharp mapping above is separate from legacy
dotnet.eval, dotnet.compile and .call. That legacy dotbridge wire converts
C# long/ulong results through a double and can lose precision; use the typed
extern provider for exact integer transport.
See provider-specific details in the C#, Python, JS/TS, Java/Kotlin, C/C++, Rust and Go guides. Dependency versions and installation policies are described in Foreign Dependencies.
DOCS / FEEDBACK
Did this page leave a question?
Tell us where the explanation lost you. Documentation is part of the language experience.