Project Guide
Create, build, run, and test Iron projects, and bring in third-party code by vendoring it.
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:
| Binary | Role | Analogy |
|---|---|---|
iron | Project tool — scaffolding, builds, runs, checks, tests | Cargo, minus the package manager |
ironc | Raw compiler — compiles single .iron files to native binaries | rustc |
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 case | Command |
|---|---|
| Create a new project | iron init |
| Build a project | iron build |
| Compile a single file | ironc build hello.iron |
| Quick script | iron 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)
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
| Field | Required | Description |
|---|---|---|
name | Yes | Package name (used for binary output filename) |
version | Yes | Semantic version string |
type | No | "bin" (default) or "lib" |
description | No | Short description of the package |
iron | No | Cargo-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.
| Flag | Description |
|---|---|
--release | Optimized build (passed through to ironc) |
--verbose | Show 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
| Flag | Description |
|---|---|
--check | Check 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!
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 directory | Every .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
| Variable | Description |
|---|---|
NO_COLOR | Disable colored output when set to any value. |
FORCE_COLOR | Force colored output even when not a TTY. |
Error Messages
| Error | Cause | Fix |
|---|---|---|
| iron.toml declares dependency '...' | The manifest still has a [dependencies] entry | Vendor the code and delete the table |
| duplicate declaration | Two vendored libraries (or a library and your code) declare the same name | Rename one of them in your copy |
| no iron.toml found | Not in a project directory | Run 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.