Ject is a small, dynamically typed language for scripts, applications, and libraries. It pairs readable, expression-friendly syntax with one tool for running code, checking projects, testing packages, managing dependencies, and building optional Rust extensions.
The current language version is 0.10.0.
const greeting = "Hello"
fn greet(name, punctuation="!")
return "$greeting, $name$punctuation"
end
print greet("Ject")
- One
jectbinary provides the REPL, runner, checker, test runner, package manager, native builder, module tree, and language server. letandconstmake mutable and stable bindings explicit without static type annotations.- Functions, closures, comprehensions, structs, pattern matching, and exceptions are built into a compact syntax.
- Source-only packages need no Rust toolchain.
- Mixed packages keep a friendly public API in Ject and put only systems work or hot paths behind a stable native ABI.
- Diagnostics use stable codes, source spans, notes, and actionable help.
Ject currently builds with Cargo:
git clone https://github.com/riffifi/ject.git
cd ject
cargo build --release
cargo install --path .
ject --versionNative packages additionally need a working Rust toolchain. Ordinary Ject scripts and source-only packages do not.
Open the REPL:
jectRun a file:
ject hello.jectCreate and run an application:
ject new hello
cd hello
ject runCreate a library instead with ject new useful --lib. Add --native when the package
needs a private Rust component.
Bindings are dynamically typed. Use let when the binding will change and const when
it must not be assigned through:
let attempts = 0
attempts += 1
const MAX_ATTEMPTS = 3
const names = ["Ada", "Lin"]
Arrays, dictionaries, comprehensions, and method-call sugar cover common data work:
let scores = {Ada: 95, Lin: 88}
let doubled = [value * 2 for value in [1, 2, 3]]
let loud = names.map(fn(name) -> name.upper())
print scores.Ada
print doubled
print loud
Functions support defaults and named arguments. Required parameters come first:
fn label(name, prefix="User")
return "$prefix: $name"
end
print label("Ada")
print label(prefix="Admin", name="Lin")
Blocks close with end:
for value in 0..5
if value % 2 == 0
print value
end
end
Ranges are end-exclusive, so 0..5 contains 0 through 4.
match is an expression. It supports literals, alternatives, comparisons, ranges,
bindings, and guards:
let score = 82
let description = match score
>= 90 -> "excellent"
60..90 -> "passing"
n when n >= 0 -> "keep practicing"
_ -> "invalid"
end
Use try, catch, and throw for recoverable failures:
fn divide(left, right)
if right == 0
throw "division by zero"
end
return left / right
end
try
print divide(10, 0)
catch error
print "failed: $error"
end
Every package is described by Ject.toml:
[package]
name = "my_app"
version = "0.1.0"
edition = "2026"
entry = "src/main.ject"
[dependencies]Add local, registry, or Git dependencies through the same executable:
ject add helpers --path ../helpers
ject add colors --version '^2.1' --registry https://packages.example
ject add widgets --git https://github.com/example/widgets --tag v1.4.0
ject installThen import the dependency by its declared package name:
import "helpers" as helpers
print helpers.greet("Ada")
Ject.lock records exact sources, versions, commits, and checksums. Use
ject install --locked in CI.
A library exports its public surface from src/lib.ject:
export const VERSION = "1.0.0"
export fn greet(name)
return "Hello, $name"
end
Consumers can use a namespace or select individual exports:
import "greeter" as greeter
import {VERSION} from "greeter"
print greeter.greet("Ada")
print VERSION
Relative imports such as import "./format" as format resolve from the importing file.
A mixed package is still a normal dependency. Its Ject facade imports a private native backend:
import "@native/image" as native
export fn resize(image, width, height)
assert(width > 0 and height > 0, "dimensions must be positive")
return native.resize(image, width, height)
end
The interpreter contains no package-specific registry for libraries such as JGUI or JNUM. Their manifests describe native artifacts, their plugins declare exports, and the loader discovers them through ordinary dependency resolution.
See Packages and native libraries for the complete authoring guide.
| Command | Purpose |
|---|---|
ject |
Start the REPL |
ject file.ject |
Run one file |
ject new NAME [--lib|--native] |
Create a package |
ject init [--lib|--native] |
Initialize the current directory |
ject run |
Run the current package entry |
ject check |
Parse and lint every package source |
ject test |
Run tests/*.ject |
ject build [--release] |
Check source and build native components |
ject add, remove, update |
Edit and resolve dependencies |
ject install [--locked] |
Resolve, lock, and build the graph |
ject publish --registry URL |
Publish an immutable package version |
ject tree |
Print the resolved source-module tree |
ject lsp |
Run the language server over standard I/O |
ject --check FILE... |
Check standalone files without running them |
ject --test FILE... |
Run explicit test files |
Run ject --help for the command summary.
- The Ject guide and language reference
- Packages and native libraries
- Runnable examples
- Release history
Ject is usable but still pre-1.0. Language and package behavior may evolve between minor versions. Registry archives and lockfiles are integrity-checked; native plugins execute with the permissions of the Ject process and must be treated as trusted code.