Compiling Executables
Build standalone executables from your Lean code. Lean compiles to efficient native code through C.
The Main Function
Every executable needs a main function:
1-- Simplest main2def main : IO Unit :=3 IO.println "Hello, World!"45-- Main with arguments6def main (args : List String) : IO Unit := do7 IO.println s!"Arguments: {args}"89-- Main with exit code10def main : IO UInt32 := do11 IO.println "Running..."12 return 0 -- 0 = success, non-zero = failureBuilding with Lake
Define an executable in your lakefile:
1-- lakefile.lean2import Lake3open Lake DSL45package myapp67@[default_target]8lean_exe myapp where9 root := `Main1# Build the executable2lake build34# Run it5lake exe myapp67# Or run directly8./.lake/build/bin/myappRelease Builds
For production, build with optimizations:
1# Build with release optimizations2lake build -Krelease34# The executable is now optimized-Kreleasefor production executables.Debug Builds
During development, use regular builds for faster compile times and better error messages. Switch to release mode only when you need performance.
Command Line Argument Parsing
Pattern matching on the args list provides a simple way to handle command-line options. For complex CLIs, consider a dedicated parsing library.
1def main (args : List String) : IO UInt32 := do2 match args with3 | [] => 4 IO.println "Usage: myapp <command> [options]"5 return 16 | ["--help"] =>7 IO.println "myapp - A sample application"8 IO.println ""9 IO.println "Commands:"10 IO.println " run Run the main task"11 IO.println " check Check configuration"12 return 013 | ["run"] =>14 IO.println "Running..."15 return 016 | ["check"] =>17 IO.println "Checking configuration..."18 return 019 | cmd :: _ =>20 IO.println s!"Unknown command: {cmd}"21 return 1Multiple Executables
A single Lake project can produce multiple executables. Each one has its ownmain function in a separate root module, but they can all share library code.
1-- lakefile.lean2import Lake3open Lake DSL45package myproject67-- Library with shared code8lean_lib MyProject910-- Main CLI11lean_exe myapp where12 root := `Main1314-- Additional tool15lean_exe mytool where16 root := `Tool.Main1718-- Test runner19lean_exe tests where20 root := `Tests.Main1# Build all2lake build34# Build specific executable5lake build myapp6lake build mytool78# Run specific executable9lake exe myapp10lake exe mytoolmain function. They can share code through library targets.Cross-Platform Considerations
1def main : IO Unit := do2 -- Check platform3 if System.Platform.isWindows then4 IO.println "Running on Windows"5 else if System.Platform.isOSX then6 IO.println "Running on macOS"7 else8 IO.println "Running on Linux/Unix"9 10 -- Platform-specific paths11 let sep := if System.Platform.isWindows then "\\" else "/"12 IO.println s!"Path separator: {sep}"Environment Variables
Environment variables let you configure your program without changing code. Read them with IO.getEnv and set them for child processes.
1def main : IO Unit := do2 -- Read environment variable3 let home โ IO.getEnv "HOME"4 match home with5 | none => IO.println "HOME not set"6 | some path => IO.println s!"Home: {path}"7 8 -- Set environment variable (for child processes)9 -- Note: This affects the current process10 IO.Process.run {11 cmd := "echo"12 args := #["$MY_VAR"]13 env := #[("MY_VAR", some "hello")]14 }Running External Commands
Lean can invoke external programs and capture their output. This is useful for integrating with existing tools or shell scripts.
1def main : IO Unit := do2 -- Run a command and get output3 let output โ IO.Process.output {4 cmd := "ls"5 args := #["-la"]6 }7 IO.println s!"Exit code: {output.exitCode}"8 IO.println s!"Output: {output.stdout}"9 10 -- Run interactively (inherits stdin/stdout)11 let exitCode โ IO.Process.spawn {12 cmd := "vim"13 args := #["file.txt"]14 stdin := .inherit15 stdout := .inherit16 stderr := .inherit17 } >>= (ยท.wait)18 IO.println s!"Editor exited with: {exitCode}"Deep Dive: Compilation Pipeline
Lean compilation works in stages:
- Lean โ IR: Lean code to internal representation
- IR โ C: IR to C code (in
.lake/build/ir/) - C โ Object: C to object files via system C compiler
- Link: Object files linked to final executable
The generated C is heavily optimized and uses reference counting for memory management.
Distribution
For distributing your executable:
1# Build release2lake build -Krelease34# The executable is self-contained5# Copy from .lake/build/bin/myapp to wherever needed67# On Windows, it's myapp.exeCreate a main that prints the number of arguments and exits.
1def main (args : List String) : IO UInt32 := do2 IO.println s!"args: {args.length}"3 return 0Example: Complete CLI Application
1def version := "1.0.0"23def printHelp : IO Unit := do4 IO.println "myapp - A command line tool"5 IO.println ""6 IO.println "Usage: myapp [options] <command>"7 IO.println ""8 IO.println "Options:"9 IO.println " --help, -h Show this help"10 IO.println " --version, -v Show version"11 IO.println ""12 IO.println "Commands:"13 IO.println " init Initialize a new project"14 IO.println " build Build the project"15 IO.println " run Run the project"1617def doInit : IO UInt32 := do18 IO.println "Initializing..."19 IO.FS.writeFile "config.json" "{}"20 IO.println "Created config.json"21 return 02223def doBuild : IO UInt32 := do24 IO.println "Building..."25 -- Build logic here26 IO.println "Done!"27 return 02829def main (args : List String) : IO UInt32 := do30 match args with31 | [] | ["--help"] | ["-h"] => 32 printHelp33 return 034 | ["--version"] | ["-v"] =>35 IO.println s!"myapp version {version}"36 return 037 | ["init"] => doInit38 | ["build"] => doBuild39 | ["run"] => 40 IO.println "Running..."41 return 042 | cmd :: _ =>43 IO.println s!"Error: Unknown command '{cmd}'"44 IO.println "Run 'myapp --help' for usage"45 return 1