Project Guide

Create, build, run, and test Iron projects, and bring in third-party code by vendoring it.

v4.0.0-alpha

This guide covers the iron project commands. For the current language syntax, including methods inside object blocks and the pub visibility model, see the language reference.

Overview

Iron ships a two-binary toolchain inspired by Rust's Cargo/rustc split:

BinaryRoleAnalogy
ironProject tool — scaffolding, builds, runs, checks, testsCargo, minus the package manager
ironcRaw compiler — compiles single .iron files to native binariesrustc

For most workflows, you only interact with iron. It reads your project manifest (iron.toml), gathers your sources (including anything vendored under vendor/), and invokes ironc behind the scenes. Iron has no package manager: there is no registry, no download step, and no lockfile. See Third-party code.

The Toolchain

iron and ironc are installed side by side in ~/.iron/bin/. The iron binary discovers ironc automatically as a sibling binary — no configuration needed.

When to use which

Use caseCommand
Create a new projectiron init
Build a projectiron build
Compile a single fileironc build hello.iron
Quick scriptiron run script.iron

iron also transparently forwards single-file commands to ironc. If you run iron build hello.iron (with a .iron file argument), it silently delegates to ironc.

Creating a Project

Binary project

$ iron init
  Created binary project `my-project`

$ tree
.
├── iron.toml
├── .gitignore
└── src/
    └── main.iron

Library project

$ iron init --lib
  Created library project `my-lib`

$ tree
.
├── iron.toml
├── .gitignore
└── src/
    └── lib.iron

iron init works in non-empty directories. It creates files that don't exist and skips files that already do — safe to run in an existing repo.

Project Structure

my-app/
├── iron.toml          # Project manifest
├── .gitignore         # Ignores target/
├── src/
│   └── main.iron      # Entry point (bin) or lib.iron (lib)
├── vendor/            # Third-party source, committed to VCS (optional)
├── tests/
│   └── test_math.iron # Test files discovered by iron test
└── target/
    ├── my-app        # Built binary
    └── combined.iron  # Concatenated source (vendored code and multi-file projects)
Conventions

Entry point is determined by convention: src/main.iron for binaries, src/lib.iron for libraries. No entry field needed.

iron.toml — Manifest Format

The manifest file describes your project. There is no dependency table; third-party code lives in vendor/ instead. Names in this guide are illustrative; replace them with your own projects.

[package]

[package]
name        = "my-app"
version     = "0.1.0"
type        = "bin"           # "bin" (default) or "lib"
description = "A cool project" # optional
iron        = ">= 4.0.0"      # optional minimum compiler version
FieldRequiredDescription
nameYesPackage name (used for binary output filename)
versionYesSemantic version string
typeNo"bin" (default) or "lib"
descriptionNoShort description of the package
ironNoCargo-style semver constraint on the iron compiler version, checked by iron build and iron run

iron init

Create a new Iron project in the current directory.

$ iron init          # binary project
$ iron init --lib    # library project

Creates iron.toml, src/main.iron (or src/lib.iron), and .gitignore. Runs git init if not already in a git repo. Skips existing files.

iron build

Build the current package.

$ iron build
   Compiling my-app v0.1.0
    Finished dev [unoptimized] in 0.42s

Reads iron.toml, gathers the sources under vendor/ and src/, and invokes ironc. It never touches the network. Output binary is placed in target/; a type = "lib" project produces target/lib<name>.a.

FlagDescription
--releaseOptimized build (passed through to ironc)
--verboseShow generated C code (passed through to ironc)

iron run

Build and immediately execute the package binary.

$ iron run
   Compiling my-app v0.1.0
    Finished dev [unoptimized] in 0.38s
     Running target/my-app
Hello, Iron!

Arguments after -- are passed to the built binary:

$ iron run -- --port 8080

iron check

Type-check the project, including vendored code, without producing a binary. Fast feedback loop.

$ iron check
    Checking my-app v0.1.0
    Finished check completed

iron test

Discover and run all .iron files in the tests/ directory.

$ iron test
     Testing my-app v0.1.0
     Running test_math.iron
     Running test_strings.iron
    Finished 2 test(s) passed

iron fmt

Format a source file in place. With --check, verify formatting without rewriting anything.

$ iron fmt src/main.iron
$ iron fmt --check src/main.iron
would reformat src/main.iron
FlagDescription
--checkCheck only — exit 0 if the file is clean, 1 if it would be reformatted, 2 on syntax errors

No Package Manager

Iron deliberately has no package manager. There is no registry, no iron add, no dependency resolver, and no lockfile. The standard library is meant to cover the common ground (collections, strings, math, I/O, time, logging, networking, HTTP), and raylib ships with the compiler.

When you do need someone else's code, you take a copy of it, the way Odin projects do: the source goes into your repository, you read it, and you own it. Builds are reproducible because everything they compile is committed, and they never reach out to the network.

Vendoring

Put third-party code in a vendor/ directory next to iron.toml. Each library gets its own subdirectory. Nothing needs to be declared in iron.toml.

my-game/
├── iron.toml
├── src/
│   └── main.iron
└── vendor/
    ├── greeter/           # an Iron library project, copied as-is
    │   ├── iron.toml
    │   ├── LICENSE
    │   └── src/
    │       └── lib.iron
    └── noise/             # or just loose .iron files
        └── perlin.iron

Grab a copy however suits you:

# copy a release
$ mkdir -p vendor/greeter
$ curl -sSL https://example.com/greeter-0.3.0.tar.gz | tar -xz --strip-components=1 -C vendor/greeter

# or track an upstream repository with git subtree
$ git subtree add --prefix vendor/greeter https://example.com/greeter.git v0.3.0 --squash

Then use it. Vendored code is compiled into the same program as your own code, so its pub functions and types are available directly:

-- vendor/greeter/src/lib.iron
pub func greet(name: String) -> String {
    return "Hello, {name}!"
}

-- src/main.iron
import greeter      -- optional: documents where the names come from

func main() {
    println(greet("Iron"))
}
$ iron run
   Compiling my-game v0.1.0
    Finished dev [unoptimized] in 0.51s
     Running target/run/my-game
Hello, Iron!
One namespace

Vendored code shares the project's namespace. import greeter is accepted, but aliased access (import greeter as g) is not supported for vendored or project-local modules yet. If two libraries declare the same name, the build fails with a duplicate-declaration error; rename one of them in your copy.

What Gets Compiled

iron build, iron run, and iron check collect every .iron file under vendor/, then the project's own src/*.iron, into target/combined.iron and compile that as one program.

In vendor/Compiled?
A directory with its own iron.toml and src/Only its src/, so a library project can be dropped in unchanged
Any other directoryEvery .iron file, recursively
tests/, examples/, target/No
Hidden directories (.git, ...)No
Non-.iron files (LICENSE, README.md, ...)No, but keep the license next to the code

Files are compiled in sorted path order. Compile errors in vendored code are reported against target/combined.iron; the -- vendor: <path> comment above each file tells you where it came from.

Updating Vendored Code

Updating a dependency is an ordinary change to your repository: replace the directory with a newer copy (or run git subtree pull), rebuild, and review the diff before you commit it. Local patches are just edits; keep a note in the vendored directory if you need to re-apply them after the next update.

Environment Variables

VariableDescription
NO_COLORDisable colored output when set to any value.
FORCE_COLORForce colored output even when not a TTY.

Error Messages

ErrorCauseFix
iron.toml declares dependency '...'The manifest still has a [dependencies] entryVendor the code and delete the table
duplicate declarationTwo vendored libraries (or a library and your code) declare the same nameRename one of them in your copy
no iron.toml foundNot in a project directoryRun iron init or cd into a project

FAQ

Why no package manager?

Package managers make it cheap to pull in large dependency trees you never read. Iron leans on a broad standard library instead, and asks you to copy the few extra pieces you need so they are visible, reviewable, and pinned by your own version control.

Do I commit vendor/?

Yes. The vendored source is part of your project. That is what makes builds reproducible and offline.

How do I share my own library?

Publish its source (a repository or a tarball) with an iron.toml and a src/ directory, as created by iron init --lib. Users copy it into their vendor/.

Can I vendor C libraries?

Not yet. extern func can call C functions that the compiler already declares (libc, raylib), but a project cannot yet declare and link an arbitrary vendored C library.