In this tutorial we will write a few Lean definitions about rectangles, prove a fact about them, generate a C library from them with lungo, and use it. The Lean project is the same as in the tutorials for Go, Python, Swift and TypeScript.
Before we start
We need:
- CMake 3.20 or later and a C11 compiler
- elan, Lean’s toolchain manager
- the
lungocommand
elan toolchain install leanprover/lean4:v4.34.1
brew install machinefabric/tap/lungo # apt, dnf, Windows, cargo: see the lungo command's reference
Write the Lean project
Make a directory with a Lake project in lean/:
mkdir shapes && cd shapes
mkdir lean
echo 'leanprover/lean4:v4.34.1' > lean/lean-toolchain
lean/lakefile.toml declares a library whose root module Lake builds by default:
name = "geometry"
version = "0.1.0"
defaultTargets = ["Geometry"]
[[lean_lib]]
name = "Geometry"
lean/Geometry.lean holds the definitions and the proof:
namespace Geometry
structure Rect where
width : Nat
height : Nat
deriving Repr
def area (r : Rect) : Nat := r.width * r.height
def scale (k : Nat) (r : Rect) : Rect :=
{ width := k * r.width, height := k * r.height }
/-- Scaling by `k` multiplies the area by `k * k`. -/
theorem area_scale (k : Nat) (r : Rect) : area (scale k r) = k * k * area r := by
simp only [area, scale]
exact Nat.mul_mul_mul_comm k r.width k r.height
/-- The rectangle with the largest area, if any. -/
def largest (rs : List Rect) : Option Rect :=
rs.foldl (fun best r => match best with
| some b => if area r > area b then some r else some b
| none => some r) none
end Geometry
Create Lake’s manifest once (it is part of the project):
(cd lean && lake update)
Generate the library
lungo.toml names the Lake project and where the C library goes:
project = "lean"
[c]
out = "geometry-c"
lungo generate
The first run builds the Lean project, which checks the proof: change k * k to k in the
theorem and lungo generate fails with Lean’s error. geometry-c is a CMake project of the
library geometry, whose API is include/geometry.h; add .lungo/ (lungo’s scratch space)
to .gitignore.
Call it from C
CMakeLists.txt of the program:
cmake_minimum_required(VERSION 3.20)
project(shapes C)
add_subdirectory(geometry-c)
add_executable(shapes main.c)
target_link_libraries(shapes PRIVATE geometry)
main.c:
#include <stdio.h>
#include <stdlib.h>
#include "geometry.h"
/* Exits with the error of a failed call. */
static lungo_value *check(int32_t status, lungo_value *result, lungo_error *error) {
if (status != LUNGO_OK) {
fprintf(stderr, "error: %s\n", lungo_error_message(error));
exit(1);
}
return result;
}
static void print_rect(const char *label, const lungo_value *r) {
char *w = lungo_value_number_string(geometry_rect_width(r));
char *h = lungo_value_number_string(geometry_rect_height(r));
printf("%s: %s x %s\n", label, w, h);
lungo_string_free(w);
lungo_string_free(h);
}
int main(void) {
lungo_value *result = NULL;
lungo_error *error = NULL;
lungo_value *r = geometry_rect_mk(lungo_value_nat(3), lungo_value_nat(4));
lungo_value *area = check(geometry_area(r, &result, &error), result, error);
char *digits = lungo_value_number_string(area);
printf("area: %s\n", digits);
lungo_string_free(digits);
lungo_value *ten = lungo_value_nat(10);
lungo_value *bigger = check(geometry_scale(ten, r, &result, &error), result, error);
print_rect("scaled", bigger);
lungo_value *items[2] = {lungo_value_clone(r), lungo_value_clone(bigger)};
lungo_value *list = lungo_value_list(items, 2);
lungo_value *best = check(geometry_largest(list, &result, &error), result, error);
if (lungo_value_get_option(best)) print_rect("largest", lungo_value_get_option(best));
lungo_value_free(best);
lungo_value_free(list);
lungo_value_free(bigger);
lungo_value_free(ten);
lungo_value_free(area);
lungo_value_free(r);
return 0;
}
$ cmake -S . -B build && cmake --build build && ./build/shapes
area: 12
scaled: 30 x 40
largest: 30 x 40
CMake downloads the lungo runtime for the platform and checks its SHA-256 digest before
linking it. Every Lean value is a lungo_value: constructors (geometry_rect_mk) take
ownership of their parts, accessors (geometry_rect_width) borrow, and each value the API
returns is freed once.
What we did
We generated a C library from a Lake project whose proofs Lean checked; it runs Lean’s compiled code on the lungo runtime. Next: