In this section

This guide shows how to use the types generated for one Lake package in the package generated for another package that requires it, so that values pass between the two without conversion.

Suppose the Lake package drawing requires the package geometry. Generated separately, each has its own type for the Lean structure Geometry.Point, and a Point from one cannot be passed to a function of the other. Telling the generation of drawing which type provides Geometry.Point (an extern type) makes it use geometry’s.

In Rust

Compile each package in build.rs, and tell the build of drawing which Rust type provides each Lean type of geometry it uses:

fn main() -> lungo_build::Result<()> {
    lungo_build::compile_lean("lean/geometry")?;
    lungo_build::configure()
        .extern_type("Geometry.Point", "crate::geometry::Point")
        .extern_type("Geometry.Shape", "crate::geometry::Shape")
        .compile_lean("lean/drawing")
}

The Rust paths are as seen from the crate root; each module is named after its Lake package:

pub mod geometry {
    lungo::include_lean!("geometry");
}

pub mod drawing {
    lungo::include_lean!("drawing");
}

drawing’s functions now take and return geometry’s types:

use my_crate::{drawing, geometry::Point};
use lungo::Int;

let a = Point { x: Int::from(-4), y: Int::from(2) };
let b = Point { x: Int::from(8), y: Int::from(6) };
let mid: Point = drawing::midpoint(a, b);

A type whose values Lean code alone can make (one with a proof among its fields) is opaque: geometry’s module refers to it as lungo::LeanValue<crate::geometry::__opaque::<Name>>, and that is the path to give.

In the other languages

Each language’s table of lungo.toml names the other package’s type for a Lean type (see extern types):

# lungo.toml of drawing
project = "lean/drawing"

[go]
out = "drawing"

[go.extern-types."Geometry.Point"]
package = "example.com/app/geometry"   # geometry's generated Go package
name = "Point"

package is what the language imports (a Go import path, a Python module, a Swift module, a JavaScript module specifier, a C header) and name the type as geometry’s generator named it. Values made by one program pass to the other as they are: by handle for opaque types, since both run on the process’s one runtime. In TypeScript every program runs in a WebAssembly instance of its own, so only types whose values are plain data can be shared there; an opaque type is refused.

What makes it safe

drawing’s compiled code reads geometry’s values at the layout it was compiled for. Both must come from the same definition of the type, and of every type its fields reach. Every generated type carries a layout fingerprint, and drawing checks geometry’s before anything runs: in Rust it does not compile against another (the provided type implements lungo::LeanLayout, which a type lungo generates does already), and in the other languages the package refuses to load, naming the type and the providing package. The fix is to regenerate both from the same Lean definition.

Types of drawing that contain a Rust extern type derive only Clone and Debug, since lungo does not know which other traits the extern type implements; add further derives with type_attribute. A mapping for a Lean type that no exported declaration uses fails with LNG0107.

In lungo-rs, compiler-tests/shaping builds two such Rust modules, and compiler-tests/extern shares types between two programs in every language.