Project Structure
A RustScript project compiles in-place: generated .rs files live alongside your .rts source files.
Layout
my-project/
rustscript.json # Project manifest
Cargo.toml # Generated by compiler, commit this
Cargo.lock # Generated by Cargo, commit this
.gitignore # Covers /src/*.rs and /target
src/
main.rts # Entry point
After building, .rs files appear next to your .rts files:
my-project/
rustscript.json
Cargo.toml
Cargo.lock
.gitignore
src/
main.rts
main.rs # Generated -- gitignored, don't edit
target/ # Cargo build artifacts (gitignored)
rustscript.json
The project manifest. Created by rustscript init, lives in the project root:
{
"name": "my-project",
"edition": "2024"
}
You can also declare explicit dependencies with pinned versions:
{
"name": "my-project",
"edition": "2024",
"dependencies": {
"clap": { "version": "4", "features": ["derive"] }
}
}
Fields:
| Field | Required | Description |
|-------|----------|-------------|
| name | Yes | Package name (used in Cargo.toml) |
| version | No | Project version, defaults to "0.1.0" |
| edition | No | Rust edition, defaults to "2024" |
| dependencies | No | Explicit dependency versions and features |
| devDependencies | No | Dev-only dependencies |
Cargo.toml
The compiler generates Cargo.toml in the project root and keeps it up to date. It is a standard Cargo manifest — commit it to git.
The compiler merges rather than overwrites: if you edit Cargo.toml directly (to pin a version, add a feature flag, or add a section), the compiler preserves your edits on the next build.
Example generated Cargo.toml:
[package]
name = "my-project"
edition = "2024"
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
The [dependencies] section is rebuilt from your imports every time you compile. Other sections you add are preserved.
src/main.rts
Your entry point. Must contain a function main() (or async function main() for async programs).
function main() {
console.log("Hello!");
}
You can organize code into multiple .rts files, but main.rts is where execution starts.
.gitignore
rustscript init generates a .gitignore that covers the right files:
/src/*.rs
/target
Generated .rs files are intentionally gitignored — they're build artifacts derived from your .rts source. The generated Cargo.toml and Cargo.lock are committed.
Templates
rustscript init supports templates for common project types:
rustscript init my-api --template web-server # axum web server scaffold
rustscript init my-cli --template cli # CLI tool scaffold
rustscript init my-wasm --template wasm # WASM project scaffold
The compilation pipeline
.rts --> Parse --> AST --> Type Check --> Lower --> Rust IR --> Emit --> .rs --> cargo build --> binary
Each stage:
- Parse -- Lexer + recursive descent parser turns
.rtsinto a RustScript AST - Type Check -- Resolves types, checks assignments, infers where possible
- Lower -- Transforms the RustScript AST to Rust IR. This is where TypeScript patterns become Rust patterns:
Array<T>becomesVec<T>,console.logbecomesprintln!, ownership is inferred - Emit -- Rust IR becomes
.rstext, written in-place next to source files - Cargo -- Standard
cargo buildcompiles the generated Rust to a native binary
Ejecting to pure Rust
Because the generated Rust is idiomatic and human-readable, you can eject at any time:
rustscript eject
This converts the project to a standard Rust project: removes rustscript.json, unstages the generated .rs files from .gitignore, and leaves you with a pure Rust codebase. No lock-in.