In this section

lungo generate --<language>_out=DIR writes a package that holds the program (as C, or for TypeScript as WebAssembly) and its API in the language. It builds with the language’s own tools and runs on the lungo runtime, which it takes from the lungo release (or a local distribution) at exactly the version that generated it. The Rust output is described in generated code and Lean types in Rust.

Below, the program’s id is its name as a C identifier (polyglot for a Lake package polyglot).

Names

An exported declaration is named after the shortest suffix of its Lean name that no other declaration of its kind ends with: Geometry.Shape.area is area, unless another function also ends with area, in which case both keep one more component (shape_area, other_area). The suffix is converted to the language’s conventions (snake_case in C and Python, UpperCamelCase for Go and all types, lowerCamelCase for Swift and TypeScript members) and escaped where it is a keyword.

A function whose name comes out the same as a type’s (or a constructor’s, or a descriptor’s) keeps one more component, as two functions ending alike do; the type keeps its name. In Go, where both are UpperCamelCase in one package, Gate.acquire beside the type Acquire is GateAcquire. Two functions that would still get the same name (foo and Foo), or two types, are an error (LNG0107) naming both: rename one in Lean.

Lean types

Lean C Go Python Swift TypeScript
Nat lungo_value *big.Int int LungoNat bigint
Int lungo_value *big.Int int LungoInt bigint
Bool lungo_value bool bool Bool boolean
UInt8–UInt32, Int8–Int32 lungo_value uint8–int32 int UInt8–Int32 number
UInt64, USize, Int64, ISize lungo_value uint64, int64 int UInt64, Int64 bigint
Float, Float32 lungo_value float64, float32 float Double, Float number
Char lungo_value rune str of length 1 Unicode.Scalar string of one code point
String lungo_value string str String string
Unit lungo_value lungo.Unit None LungoUnit null
ByteArray lungo_value []byte bytes [UInt8] Uint8Array
FloatArray lungo_value []float64 list[float] [Double] number[]
Option α lungo_value lungo.Option[A] A or None A? A or null
List α, Array α lungo_value []A list[A] [A] A[]
α × β lungo_value lungo.Pair[A, B] tuple[A, B] (A, B) [A, B]
Except ε α lungo_value lungo.Except[E, A] lungo_py.Ok / lungo_py.Err LungoExcept<E, A> { ok, value } / { ok, error }
α → β lungo_value func(A) (B, error) callable (A) throws -> B function
a structure lungo_value struct frozen dataclass struct object
an inductive type lungo_value sealed interface, a struct per constructor base class, a frozen dataclass per constructor indirect enum objects tagged with kind
a polymorphic type lungo_type argument generic type Generic class generic type generic type
an opaque type lungo_value struct around lungo.Opaque subclass of lungo_py.Opaque struct around LungoOpaque subclass of Opaque
any other type lungo_value lungo.Opaque lungo_py.Opaque LungoOpaque Opaque

Numbers are checked when they cross into Lean: a negative Nat, a Go int32 out of a UInt16’s range, a string that is not valid Unicode, a nil value of an inductive type are rejected (see errors), never truncated. An Option whose values can themselves be None/null (Option (Option α), Option Unit) is Some(value) (Python, TypeScript) where it is some. A structure that contains itself by value (through options, pairs or other structures) is a pointer in Go and a final class in Swift. Values Lean cannot give a language type (IO actions stored as data, a Type, a value of a type the program does not describe) are opaque: handles that keep the Lean value alive until they are closed or collected.

Opaque types

An inductive type whose values Lean code alone can make (one with a proof among its fields, say, like a structure of a value and the proof that it is well formed) is opaque: its values cross as handles, which the program’s functions make and read. It is still a type of its own: Wf in Go is struct{ lungo.Opaque }, a subclass of lungo_py.Opaque in Python, a struct in Swift, a subclass of Opaque in TypeScript, so one opaque type’s value cannot be passed where another’s is expected. Type parameters of an opaque type are not part of its binding type. Types that are not inductive, or have indices, are plain opaque values.

Layout fingerprints

Every named type records its layout fingerprint: <Name>Fingerprint (Go), <Class>.__lungo_fingerprint__ (Python), <Type>.lungoFingerprint (Swift), leanTypes["<Lean name>"].fingerprint (TypeScript), <ID>_<TYPE>_FINGERPRINT (C), and lungo::LeanLayout::FINGERPRINT in Rust. A package using another’s type as an extern type checks it before it runs.

A polymorphic function takes a type descriptor for each type parameter, first: Size(lungo.NatType, tree) (Go), size(lungo_py.NAT, tree) (Python), size(Lungo.nat, tree) (Swift), p.size(L.NAT, tree) (TypeScript), polyglot_size(nat_type, tree, &result, &error) (C). Each named type has a descriptor function (TreeType(a), tree_type(a), Tree.lungoType(a), treeType(a), polyglot_tree_type(a)).

Errors

Lean result C (status) Go Python Swift TypeScript
a value LUNGO_OK (T, nil) the value the value the value
IO α failing LUNGO_FAILED, LUNGO_ERROR_IO *lungo.IOError lungo_py.LeanIOError LungoIOError LeanIOError
EIO ε α failing LUNGO_FAILED, LUNGO_ERROR_VALUE *lungo.Error[E] lungo_py.LeanError LungoError<E> LeanError
arguments Lean cannot represent LUNGO_MALFORMED *lungo.MalformedError lungo_py.MalformedError LungoMalformed MalformedError

A function whose result is Unit returns nothing (only its error) in Go, Swift and TypeScript.

Facilities

The externs the host implements are the operations of the program’s facilities, which its Lean code declares (@[lungo_facility "ns.name"], @[lungo_operation C]; see how to call host code from Lean). Each facility is one type in the language, named after the last component of its identifier (polyglot.scaler is Scaler), with one method per operation, and the host installs an implementation of each:

Language A facility Installing it A call before every facility is installed
C functions <id>_implement_<facility>_<operation>(f, ctx, drop) each function, before the program initializes ends the process, naming the facility and the operation
Go interface Scaler SetScaler(impl) returns *lungo.MissingFacilityError (Facility, Operation)
Python typing.Protocol Scaler set_scaler(impl) raises lungo_py.MissingFacilityError (facility, operation)
Swift protocol <Module>Scaler setScaler(_:) throws LungoMissingFacility (facility, operation)
TypeScript interface <Program>Scaler, a member of <Program>Facilities load({ facilities: { scaler: … } }) load rejects with MissingFacilityError (facility, operation)

Every facility the program’s linked modules declare must be installed, whether or not a given call reaches it, so a program fails at its first call rather than in the middle of one. A facility’s documentation comment names the assumptions the program’s claims make of the host’s implementation; the program cannot check them. A method whose Lean type is pure must answer the same arguments the same way: Lean may evaluate a call once and keep the value.

Async exports

An export returning Lungo.Async.Program Op α asks the host to perform operations of the async facility over Op as it runs, and waits for each answer. It is an asynchronous function of the language taking a handler first: an object with one method per constructor of Op, which takes the constructor’s fields and returns (or resolves to) the answer.

Language Handler The export Giving the program up
C — int32_t <id>_<fn>(arguments…, lungo_step *step, lungo_error **error) returns the program’s first step; the host answers each with lungo_resume (see the C API) lungo_async_cancel
Go interface <Op>Handler, methods taking a context.Context first and returning (answer, error) F(ctx, handler, arguments…) (T, error) ctx cancelled, a method’s error, or a panic
Python typing.Protocol <Op>Handler; a method returns the answer or an awaitable of it async def f(handler, arguments…) the task cancelled, or a method’s exception
Swift protocol <Module><Op>Handler (Sendable), methods async throws func f(_ handler: some <Module><Op>Handler, arguments…) async throws -> T the task cancelled, or a method’s error
TypeScript interface <Op>Handler; a method returns the answer or a promise of it p.f(handler, arguments…, { signal }) returns a promise signal aborted, or a method’s rejection

A program given up is released by the runtime, and the call fails with the method’s error (or the cancellation). lungo.Outstanding() (Go), lungo_py.outstanding() (Python), lungoOutstanding() (Swift), p.outstanding() (TypeScript, per program) and lungo_async_outstanding() (C) count the programs waiting for an answer: zero once every call has completed. A process may end with programs waiting.

Assurance

Every package carries the program’s assurance document, assurance.json, the same file for every language, and reads it as the language’s types:

Language The document
C const char *<id>_assurance_json(void): its text, byte for byte
Go Assurance() *lungo.Assurance (embedded with //go:embed); Claim(name), Export(name); lungo.ParseAssurance reads another
Python ASSURANCE, a lungo_py.Assurance; claim(name), export(name); lungo_py.Assurance.from_json reads another
Swift assurance, a LungoAssurance, and assuranceJSON, its text byte for byte; claim(_:), export(_:)
TypeScript ASSURANCE, a frozen L.Assurance; L.parseAssurance reads another

Each reader refuses a document of another schema version, or with a field it does not know or lacks, at any depth (details).

Threads

The program may be called from any number of threads at once (Go goroutines, Python threads, Swift tasks and queues, C threads); calls release the Python GIL. Host functions may be called on any thread, including threads Lean’s tasks run on. WebAssembly has one thread: Lean’s tasks run when they are spawned, and the program cannot use child processes, sockets, signals or timers (LNG0408).

One process has one lungo runtime and one host: the support library of its language. The C value API’s host functions and another language’s support library cannot be used in the same process.

C

CMakeLists.txt          the library <id> (target <id>::<id>) and the runtime
include/<id>.h          the API
src/<id>.c
program/                the program: lungo.h, <id>_program.h, <id>_program.c,
                        <id>_boundary.c, modules/*.c

The CMake project downloads the runtime archive of the lungo release for the target, checks its SHA-256 digest (URL_HASH), and links its static library (lungo::runtime). With -D<ID>_FETCH_LUNGO=OFF it uses an installed runtime instead (find_package(lungo <version> EXACT); CMAKE_PREFIX_PATH or lungo_DIR locates it); -D<ID>_LUNGO_TARGET=<triple> chooses the archive when the target cannot be inferred. Include it with add_subdirectory.

Every value is a lungo_value * of the C API. The API adds, per type, its descriptor (<id>_<type>_type(...)), a constructor function per constructor (<id>_<type>_<ctor>(fields…), taking ownership of the fields), a macro of each constructor’s index (<ID>_<TYPE>_<CTOR>) and an accessor per field (<id>_<type>_<field>(v), or <id>_<type>_<ctor>_<field>(v) for types of several constructors); per function int32_t <id>_<function>(type arguments…, arguments…, lungo_value **result, lungo_error **error) (an async export takes lungo_step *step instead of result); <id>_implement_<facility>_<operation>(f, ctx, drop); <id>_assurance_json(); <id>_initialize() and <id>_run_main(argc, argv). Objective-C uses the same API (see Swift).

Go

<id>.go                 the package (cgo)
*.c, *.h                the program; module files are module_<stem>_lean.c

The package imports github.com/machinefabric/lungo-go and requires exactly the module version of the lungo release that generated it (const _ = lungo.EnforceVersion<version> does not compile with another). The module carries the runtime for linux/amd64, linux/arm64, darwin/amd64, darwin/arm64 and windows/amd64 and links it once per binary. Build with cgo enabled and a C compiler (on Windows, MinGW-w64 GCC). The package name is the program’s id (option package).

Functions return (T, error); types are Go types with a descriptor function each (PointType(), TreeType[A](typeA)); Set<Facility>(impl) installs each facility; async exports take a context.Context and a handler; Assurance() is the assurance document; RunMain(args) (int, error) runs main.

Python

pyproject.toml          scikit-build-core; requires lungo-py
CMakeLists.txt          builds the program as the shared library of the package
program/
src/<package>/__init__.py, py.typed

pip install DIR builds a wheel (py3-none-<platform>: the program is a shared library loaded with ctypes, not a CPython extension, so one wheel serves every Python 3). It depends on exactly the lungo-py of the release, which carries the runtime. Functions are module functions (async exports are async def); types are frozen dataclasses; set_<facility>(impl) installs each facility; ASSURANCE is the assurance document; run_main(args) runs main.

Swift

Package.swift           depends on lungo-swift at exactly the release's version
Sources/<Module>Program/  the program and the C API (include/<id>.h: for C and Objective-C)
Sources/<Module>/<Module>.swift

The package has two products: <Module> (the Swift API) and <Module>Program (the C API, which Objective-C imports with @import <Module>Program;). lungo-swift provides LungoKit and the runtime as an XCFramework (macOS 12 and later, iOS 15 and later, the iOS simulator). Functions are module functions that throw (async exports are async throws); types are structs and indirect enums; set<Facility>(_:) installs each facility; assurance is the assurance document; runMain(_:) runs main.

TypeScript

package.json            depends on lungo-ts at exactly the release's version
index.js, index.d.ts    ES module and its declarations
program.wasm            the program and the runtime, for wasm32-wasip1

lungo generate links program.wasm with the pinned wasi-sdk. await load(options) instantiates it (on Node.js 20 and later with node:wasi; in browsers with lungo-ts’s BrowserWasi, or any WASI given as options.wasi) and returns an object whose methods are the program’s functions (async exports return promises); options.facilities implements the facilities, and ASSURANCE is the assurance document. Programs using primitives WebAssembly lacks cannot be generated (LNG0408).

Embedded packages

A library that already is a package in its language (a Python distribution, an npm package, a Swift package, or a CMake project) embeds the program in it with the generator option embed instead of depending on a package of the program’s own. lungo owns the output directory and replaces it as a whole, so it is a directory of the host’s, never its root. The host’s manifest provides what the generated one would have:

Language The output directory The host provides
C (embed=true) include/, src/, program/, and a CMakeLists.txt defining the library <id> the target lungo::runtime (find_package(lungo <version> EXACT CONFIG)) before add_subdirectory; linking the packages providing extern types to <id>
Python (embed=<module>) the module: __init__.py, program/, and a CMakeLists.txt building the program and installing it into the module a scikit-build-core build whose CMakeLists.txt adds the directory with add_subdirectory; lungo-py==<version> in build-system.requires and dependencies
Swift (embed=true) <Module>Program/ and <Module>/, the two targets the targets, by path: <Module>Program depending on LungoKit with cSettings: [.headerSearchPath("program")], and <Module> depending on it and LungoKit; lungo-swift at exactly the release’s version
TypeScript (embed=true) index.js, index.d.ts, program.wasm lungo-ts at exactly the release’s version, and program.wasm in the published files