Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Toolchain Commands

The flint CLI has six commands. That is the entire toolchain.

flint new

Scaffold a new project:

flint new blink

Creates:

blink/
  flint.toml
  src/
    main.fl

The generated project targets rp2040 with board = "pico" by default.

flint fmt

Format your source into canonical Flint style:

flint fmt                     # format project in current directory
flint fmt --path my_project   # format a specific project path

The formatter is opinionated and non-negotiable, like gofmt. Run it before committing. There is no configuration.

Key formatting rules:

  • 4-space indentation
  • 100-character line limit
  • One blank line between top-level items
  • Brace on the same line as the construct
  • use declarations at the top of the file, sorted

flint check

Type-check and semantically validate your project without building:

flint check                    # check project in current directory
flint check --path my_project  # check a specific project
flint check --json             # emit diagnostics as JSON

check runs the full frontend: lex, parse, resolve, and type-check. It is fast, so use it often. No artifacts are written.

The --json flag is useful for editor integrations and CI scripts:

flint check --json | jq '.diagnostics[] | select(.severity == "error")'

flint build

Compile and produce artifacts:

flint build                            # build project in current directory
flint build my_project                 # build a project by path
flint build --output-format uf2        # produce a UF2
flint build --output-format elf        # produce an ELF
flint build --output-format bin        # produce a raw binary
flint build --show-layout                  # show section layout and symbol table
flint build --show-layout --json           # same, as structured JSON

Successful output:

compiling project `blink` (rp2040-thumb-pico)
compiled successfully in 0.01s
binary image: 1,108 bytes (1.08KB)
output: `build/blink.uf2` 2,560 bytes (2.5KB)

With --show-layout:

compiling project `blink` (rp2040-thumb-pico)
compiled successfully in 0.01s
binary image: 1,108 bytes (1.08KB)
output: `build/blink.uf2` 2,560 bytes (2.5KB)
layout:
  total size: 1,108 bytes (1.08KB)
  base address: 0x10000000
  entry address: 0x100001C1
  output: uf2 (2,560 bytes (2.5KB)) -> build/blink.uf2
  sections:
    0x10000000  +0x000000  256 bytes  boot2 (second-stage bootloader)
    0x10000100  +0x000100  192 bytes  vector_table (48 vectors)
    0x100001C0  +0x0001C0  660 bytes  text (reset handler and reachable functions)
    0x100001C0  +0x0001C0  504 bytes  reset_handler (startup and clock bring-up)
  symbols:
    0x100001C0  +0x0001C0  504 bytes  reset_handler (entry)
    0x100003B8  +0x0003B8   40 bytes  src/main.fl::main (src/main.fl)
    0x100003E0  +0x0003E0   44 bytes  std/time.fl::sleep_ms (std/time.fl)
    0x1000040C  +0x00040C   44 bytes  micro/gpio.fl::into_output (micro/gpio.fl)
    0x10000438  +0x000438   28 bytes  micro/gpio.fl::toggle (micro/gpio.fl)

--show-layout breaks down exactly what landed in your binary. Sections show how flash is organized. Symbols show every function that made it into the image, its address, and its size. Notice only the functions your program actually calls are listed. Everything else was discarded. Useful for understanding your flash budget or tracking down an unexpected size increase.

MCU Recursion

On MCU targets, recursive function calls are an error by default. Stack overflow is unrecoverable on most MCUs, and recursion makes stack usage unbounded and impossible to analyze statically.

This is not a novel restriction. The JSF C++ Coding Standards, used for safety-critical avionics software on platforms like the F-35, explicitly prohibit recursion for exactly the same reason: if you cannot bound the stack at compile time, you cannot guarantee the system will not corrupt itself at runtime. Flint enforces this by default and lets you opt out only when you know what you are doing.

To opt into recursion intentionally:

flint build --allow-recursion

Or set it in flint.toml:

allow-recursion = true

flint lsp

Start the language server for editor integration:

flint lsp                          # start the server (stdio transport)
flint lsp --log-file /tmp/lsp.log  # write debug log to a file
flint lsp --trace protocol         # trace protocol messages to stderr

Editors launch this automatically. You normally do not run it yourself. See Language Server for supported features and editor setup.

flint flash

Reserved for direct device flashing. Not yet implemented.

In the meantime, use one of these approaches:

  • UF2 drag-and-drop: BOOTSEL mode, copy .uf2 to the drive.
  • picotool: picotool load build/blink.uf2 --force
  • probe-rs: probe-rs run --chip RP2040 build/blink.elf
  • OpenOCD: openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg ...

Common Workflows

# Quick check during development
flint check

# Build and flash (UF2 method)
flint build --output-format uf2
cp build/blink.uf2 /Volumes/RPI-RP2/

# CI check
flint check --json

# Debug build with verbose layout
flint build --output-format elf --show-layout

Diagnostics

Flint errors include file, line, and column. Errors are reported to stderr. The --json flag on check and build emits structured diagnostic objects for tooling:

{
  "diagnostics": [
    {
      "severity": "error",
      "message": "use of moved value: `packet`",
      "file": "src/main.fl",
      "line": 14,
      "column": 5
    }
  ]
}