Language Reference
<!-- verified-against: seed-llvm; examples: docs-examples-test -->
This page is the user-oriented reference for the current
seed Language Specification. The complete grammar and
operational semantic chapters live under compiler/llvm. For current limits
and closure claims, see Current Status.
Source encoding and names
.sd files are UTF-8. Comments and string contents may contain Unicode.
string owns valid UTF-8 and str is a borrowed read-only UTF-8
pointer-plus-byte-length view. Text length is measured in bytes; Unicode scalar,
grapheme, normalization, and locale operations belong in libraries.
Source identifiers intentionally use ASCII:
| Kind | Rule | Example |
|---|---|---|
| variable, function, field, module | [a-z][a-z0-9_]* |
my_document |
| struct, enum, union, trait, generic parameter | [A-Z][a-zA-Z0-9]* |
Document, T |
| enum variant | [A-Z][a-zA-Z0-9]* |
Some, Ok, Red |
| constant | [A-Z][A-Z0-9_]* |
MAX_DOCUMENT_SIZE |
The formal language name is seed, always lowercase. Value names use lower snake
case; nominal types and enum variants use UpperCamelCase and match
[A-Z][a-zA-Z0-9]*. Constants use upper snake case. _ is the discard binding;
a leading underscore may mark an intentionally unused private binding.
Line comments start with //. Block comments /* ... */ nest. The clean
grammar does not use # comments.
The source loader validates the whole file as UTF-8 before lexing and rejects embedded NUL; decoded string literals are validated as well.
Bindings, constants, and mutation
const MAX_COUNT: usize = 100
fn release(ticket: i64) {}
fn main() -> i64 {
let answer = 42
let mut count: usize = 0
count += 1
let linear ticket = 7
release(ticket)
if count < MAX_COUNT { answer } else { 0 }
}
The current compiler also accepts typed scalar expressions, private fixed
u32 constant tables such as const TABLE: [4]u32 = [1 as u32, 2 as u32, 3 as u32, 4 as u32], static text, tuples, and structs. Indexed and field reads
use immutable static storage with ordinary bounds checks. Public aggregate
values retain their recursive representation in schema-14 source-elided builds.
The constant type annotation is optional, matching const_decl = "const", constant_ident, [ ":", type ], "=", expression: an unannotated constant infers
its type from its initializer (const MAX_ITEMS = 64 is i64,
const OFFSETS = [1, 4, 9] is [3]i64), including static text, tuples, structs
and named constants. An initializer whose type cannot be inferred needs an
explicit annotation and is rejected with R037 at the declaration.
Bindings are immutable unless declared mut. Assignment to a binding, field,
or index through a binding requires that binding to be mutable. linear
creates an exactly-once obligation.
let also accepts irrefutable patterns: tuple destructuring
(let (a, b) = pair), struct destructuring
(let Point { x, y } = origin), wildcards (_), and nesting. Each named
leaf becomes an ordinary binding moved out of the initializer's component,
so ownership, linearity, and cleanup track components independently.
Refutable patterns (variants, literals) are rejected
(error[B043]); use match for those.
A let annotation accepts any type valid in a parameter position (arrays,
slices, tuples, pointers, function types, generics, qualified names) and is
checked against the inferred initializer type
(error[H059] on mismatch). Compile-time layout queries size_of(type) and
align_of(type) are evaluated against the active target and are valid in
constant contexts — const initializers and array lengths such as
[size_of(i64)]u8; elsewhere they are rejected with error[H129].
To discard a return value, use let _ = expression which evaluates and
cleans up the value without creating a binding; repeated _ in one scope is
legal and _ is never accessible. let _unused = expression with a leading
underscore also marks an intentionally unused binding but creates a named
binding.
Functions, methods, and returns
struct Document {
title: string
}
fn add(left: i64, right: i64) -> i64 {
left + right
}
@borrow
fn Document.title_size() -> usize {
len(self.title)
}
@mut
fn Document.rename(next: string) {
self.title = move next
}
unsafe fn read_register(address: *mmio mut i64) -> i64 {
raw_volatile_read<i64>(address, 0)
}
A block's final expression is its value. return expression returns early; the
clean compiler does not use ret. A method declaration includes the nominal
type before the dot and receives implicit self; do not write self in its
parameter list. fn TypeName.drop() is a consuming destructor. @borrow on a
method or trait method gives self a shared lexical loan. @mut instead
requires a mutable addressable receiver, holds an exclusive loan for the call,
and makes mutations to self visible to the caller. The two attributes are
mutually exclusive.
str returns are provenance-based, not restricted to methods: a free function
may return a static or input-derived view. A view derived from a local owned
string/bytes value cannot escape (V001); return the owner when ownership
must leave the function. There is no lifetime syntax.
Public declarations use pub. Foreign declarations use explicit extern
calling convention/link metadata and are unsafe to call until wrapped by an
audited safe function. Platform-C argument classification is target-specific:
Windows x86-64 passes aggregate parameters larger than eight bytes indirectly,
so str, slices, and equivalent C structs interoperate with MinGW/MSVC-style
callees.
Core types
| Category | Types |
|---|---|
| control | void, never, bool |
| integer | i8, i16, i32, i64, u8, u16, u32, u64, isize, usize; byte aliases u8 |
| float | f32, f64 |
| text/buffer | str, string, bytes, shared_bytes |
| formatting | format_args (ephemeral) |
| aggregate/view | [N]T, []T, mut []T, tuples, nominal structs/enums/unions |
| failure | ?T, Result<T, E>, alloc_error |
| callable | non-owning fn(T) -> U, escaping box fn(T) -> U |
| low-level | raw pointers plus user, kernel, physical, mmio, and dma address spaces |
| exact-once | linear T |
AllocResult<T> abbreviates Result<T, alloc_error> without introducing a
new type identity, representation, cleanup rule, or ABI.
Hosted main may return Result<i64, E>. Ok(status) exits with that status;
alloc_error uses the fixed allocation diagnostic. Another E must be Copy,
have no destructor, and implement @borrow fn E.display() -> str; its view is
written once before status 1, after ordinary cleanup.
@static_view extern fn ... is the audited FFI declaration for a returned
str/slice whose storage is process/static lifetime. The attribute is rejected
on ordinary seed functions and does not make the foreign call safe.
Effect attributes may annotate fn declarations: @no_alloc, @allocates,
@blocking, @panics, @io, @interrupt_safe. Qualified callback types
use @no_runtime_effects fn(...) -> T to declare a complete empty effect
summary. An unsafe struct may declare @unsafe_send to assert the send
capability, or @repr("null_niche") to enable null-pointer optimization on a
single raw-pointer field for ?Wrapper.
Integer add/subtract/multiply wraps deterministically at the type width.
Division by zero, signed minimum divided by -1, invalid shifts, invalid safe
casts, and failed safe indexing trap. Release profiles preserve these rules.
Explicit integer widening uses source signedness; narrowing keeps low bits and
equal-width signedness changes preserve bits. Integer/float and float-width
casts follow IEEE-754 conversion. Float-to-integer requires unsafe and a
finite destination-representable input.
Byte literals use b'A' and have type u8. One raw ASCII byte or one
\\n/\\r/\\t/\\0/\\\\/\\'/\\xNN escape is accepted. Use
b'\\xFF' for a non-ASCII byte; seed deliberately has no locale-dependent
character literal type.
Integer literals are decimal, 0x hexadecimal, or 0b binary, with _ as an
optional digit separator (20_000_000, 0xFF, 0b1010). Every accepted form
reaches codegen everywhere a literal is allowed, including array literals and
repeat literals.
A repeat array literal fills a fixed array from one literal:
let slots: [4]usize = [0; 4], [255; 3], [[0; 2]; 3]. The element type
comes from the annotation or the array's expected type, so no cast is needed.
The value must be a literal (or a cast of one, or an array of those) — a place
or call value is rejected as B048 rather than silently evaluated once per
element — and the count must be a positive integer literal (B049) of at most
65536 (B050), because the elements are materialized individually; larger
fills belong to a heap buffer or an explicit loop.
Structs, ADTs, and unions
struct Document {
title: string
}
enum LookupResult {
Found(Document)
Missing
}
union ScalarBits {
integer: u64
decimal: f64
}
trait Identity {
fn id() -> i64
}
A struct literal is Document { title: owned_title }. An ADT constructor is
qualified, for example LookupResult.Found(value), and its payload is
inspected through exhaustive match. A payload-free enum:
enum Color { Red Green Blue }
Raw union construction/access is unsafe. Safe tagged alternatives use ADTs.
Options, results, and propagation
fn find(enabled: bool) -> ?i64 {
if enabled { Some(42) } else { None }
}
fn allocate_name(view: str) -> Result<string, alloc_error> {
view.to_string()
}
Some(value) and expected-type None construct an option. Ok(value)
and Err(problem) construct a result. ? consumes the container,
yields its success/present payload, or returns the corresponding failure from
the current function. Result propagation may change the success type but must
preserve the error type. It performs no implicit allocation or clone.
is_some/is_none and is_ok/is_error inspect by shared borrow.
unwrap_or(fallback) consumes the container and cleans the unselected owned
value exactly once. or_abort(), expect(message), and main-only
or_exit(status) are explicit application boundaries; they never hide inside
ordinary ? propagation.
? can cross a spawn boundary when the body is fallible: spawn { fallible_work()? } stores Result<T, E> into a join cell and the spawn
expression yields a task_join handle whose wait() returns Result<T, E>;
payload and error must be copy types without destructors. A ? inside a
closure nested in a void spawn body is still rejected.
Control flow and patterns
The implemented control forms are expression-oriented if/else if/else,
while, loop, for, exhaustive match, blocks, break, continue, and return.
else if is the required spelling; elif is not accepted.
Match arms need no commas unless the boundary is ambiguous: an arm whose body
ends in an expression followed by a (/[ pattern requires the comma
(B044). Patterns cover bindings/discards, literals, tuples,
structs, and qualified ADT variants; guards preserve a move-only scrutinee until
the selected arm actually transfers it. _ is a true discard binding:
let _ = expr evaluates and cleans up without binding, repeated _ in one
scope is legal, and _ is never accessible. ref is a contextual pattern
modifier that binds a move-only payload by read-only borrow when the scrutinee
is a borrowed place (Some(ref info) on a @borrow self field); ref bindings
cannot be moved or mutated, ref _ discards without moving, and a match whose
every move-only payload is ref-bound does not consume the scrutinee. Temporary
scrutinees whose arms transfer move-only payloads are materialized into an
implicit owned binding, so match fallible() { Ok(v) => ... } compiles without
a let x = expr wrapper.
if let and while let are the concise forms over match: if let Some(x) = found { ... } else { ... } and while let Some(x) = next { ... }. Both
desugar to a two-arm match, so binding, move/borrow behavior, and cleanup are
exactly the match rules. if let Some(...) names its complement None; any
other variant pattern keeps a wildcard arm, and H032 rejects that when the
scrutinee carries a move-only payload — write the explicit match arms there.
An irrefutable pattern (while let name = value) is rejected as H029.
for over ranges, fixed arrays, and slices lowers to an allocation-free
index/length loop. for index, value in enumerate(items) adds a usize index
without a heap iterator. Move-only elements require explicit
indexing/ownership handling.
Text, views, and conversions
- literals have static borrowed type
str; - owned
stringautomatically borrows asstrwhen expected; lenreturnsusizefor text, buffers, slices, and arrays;string.view()returnsstr;bytes.view()andshared_bytes.view()return[]u8;bytes.mutable_view()returns an exclusivemut []u8with the full allocated length;bytes.zeroed(length)fallibly allocateslengthzero-initialized bytes and itsmutable_view()covers the full allocation;byte_buffer.mutable_view()returns only the initialized prefix (length 0 for a fresh buffer), not the capacity — usebytes.zeroedwhen a full-length mutable write target is needed;bytes.zeroed(length)requiresi64and fallibly allocateslengthzero-initialized bytes;- fixed arrays derive compatible shared views and mutable arrays derive exclusive views;
str.slice(start, count)is checked and zero-copy;- text slices also check UTF-8 code-point boundaries;
str.equal(other)and checkedstr.find_ascii(delimiter, start)are allocation-free;compiler/llvm/libs/textbuilds a source-provenance-safe split cursor from returned byte ranges;[]u8.slice(start, count)is a checked zero-copy subview and[]u8.equal(other)compares length and contents without allocation;str.to_string()/string.from(str)returnResult<string, alloc_error>;str.to_bytes()/bytes.from(str)returnResult<bytes, alloc_error>;bytes.to_shared()/shared_bytes.from(bytes)consume the bytes owner and returnResult<shared_bytes, alloc_error>.bytes.into_prefix(length)consumes the owner, checkslengthagainst the initialized length, and returns the same allocation with a shorter logical length. It does not allocate or copy; the original owner is moved.[N]T.mutable_view() -> mut []Tderives an exclusive mutable view when the array owner is mutable.
Typed phase-1/2 interpolation is allocation-free unless explicitly materialized:
use "file"
fn main() -> Result<i64, alloc_error> {
let name = "widget"
let count = 3
write_stdout_format(f"name={name} count={count} braces={{ok}}\n")
let message = f"count={count}".to_string()?
Ok(len(message) as i64)
}
Expressions evaluate once from left to right. Supported values are str,
borrowed string, bool, and builtin integers — as arbitrary expressions,
not just names: calls, operators, and nesting all work (f"{a + b}",
f"{p.distance()}"). The ephemeral format_args
result must be passed directly or consumed by .to_string()?; it cannot be
assigned with let, returned, captured, or stored. Integer/text/bool width,
fill, alignment and integer base specifications are implemented. Custom types
use trait Display { @borrow fn display() -> str } with direct static dispatch.
Floats support default general output plus f/F, e/E, and g/G with
precision through 18. Custom :? formatting uses the analogous borrowed
Debug.debug() -> str trait.
Generics and traits
Generic parameters use UpperCamelCase. Definitions are checked against their
bounds, then concrete reachable instances are monomorphized. A parameter may
declare multiple trait bounds with + (fn f<T: Metric + Named>(x: T)); every
bound must name a declared trait, the argument must implement all of them
(error[H072] per missing bound), and trait methods resolve through any bound.
Impl blocks may themselves be generic: impl<T: Show> Show for Pair<T>
implements the trait for every instance whose arguments satisfy the impl's
bounds; the target arguments must be exactly the impl's parameters in order
(error[P042]), and a generic impl overlapping another impl of the same
trait for the same type is rejected (error[R019]).
Trait dispatch is
static and direct: there are no implicit vtables, boxing, allocation, or clone.
Impl completeness, signatures, coherence, and orphan rules are checked.
Public generic templates, common instances, ephemeral format_args
parameters, null-niche nominal representation, exclusive mutable receivers,
qualified callbacks, and boxed callable signatures persist through current
schema-17 .sdi. Materialized method instances are exported only by an
interface that also owns the generic method definition. A wrapper around an
imported generic type therefore records its public contract without an orphan
specialization; a source-elided consumer obtains the method template from the
wrapper's dependency interface and materializes the concrete instance there.
inline fn requests bounded inlining. A pub inline fn publishes its verified
body and deterministic body hash in schema-13 .sdi while retaining an
exported fallback symbol. Inline declarations have a 4096-byte source budget;
a body change invalidates dependent artifacts. Mutable slice partitions use
view.split_at_mut(index) and tuple patterns may declare mutable bindings as
(mut left, mut right). The split checks index <= len, allocates nothing,
and keeps the common owner exclusively loaned until both siblings die.
Closures and callbacks
fn apply(values: [3]i64, callback: fn(i64) -> i64) -> i64 {
let mut total = 0
for value in values {
total = total + callback(value)
}
total
}
fn main() -> i64 {
let base = 40
let total = apply([1, 2, 3], |value: i64| base + value)
let retained = (box |value: i64| base + value).or_abort()
total + retained(1)
}
|...| and prefix-position || create anonymous callables. Parameter types
may be omitted when an expected fn(...) -> T supplies them. A plain closure
uses stack capture storage, allocates nothing, and cannot escape its lexical
owner. Copy-capable captures copy; owned captures move; captures are immutable.
Borrowed, mutable, region-bound, already-moved, and other unsafe-to-store
captures are rejected.
box |...| is the explicit escaping form and returns
Result<box fn(...) -> T, alloc_error>. The owner may be stored or returned,
but is move-only, non-cloneable, and non-sendable. Drop destroys its captures
exactly once and frees its allocation. Region allocation remains lexical.
There is no implicit conversion between stack and boxed closures.
Seed fn(...) -> T callbacks use a one-pointer code/environment descriptor.
C/platform function pointers stay thin and accept named functions only.
Closure effects are inferred from the body and checked against qualified
callback contracts such as @no_runtime_effects fn() -> void. Recursive
closures are unsupported.
Ownership summary
- scalars and copy-only aggregates copy;
- resources move and make the source place unavailable;
.clone()is explicit and capability/effect checked;- borrowed views carry provenance without lifetime syntax;
- structured cleanup runs exactly once on normal structured exits;
- panic/trap/abort does not unwind or run destructors;
linear Tmust be consumed exactly once;- partial ownership is tracked through fields, nested fields, known array
indexes, active ADT payloads, unions, generics, channels, function values, and
.sdi; dynamic-index resource moves remain rejected.
See Ownership for the operational model.
Regions, tasks, and libraries
region(capacity) { body } installs a lexical checked bump allocator. Values,
views, or pointers tied to the region cannot escape. Structured tasks captured
inside a region join before its backing storage is released.
spawn { body } is structured: every edge leaving the owning lexical scope
waits for its task group before cleanup. Capture is copy or move by capability.
A fallible body (? inside, or a tail of type Result<T, E>) yields a typed
join: the outline stores the outcome in a join cell and handle.wait()
returns Result<T, E>; task_result_* remains for manual joins.
Channels, typed joins, events, cancellation, and synchronization are provided
by the ordinary compiler/llvm/libs/task package. The clean grammar does not
have built-in chan<t>, send, recv, or detached spawn.
use "name" imports a source module or a source-elided .sdi interface.
Imported public functions are callable with or without their module
qualifier: use "process" makes both environment("HOME") and
process.environment("HOME") valid. An unknown qualified symbol reports
error[H002]: unknown symbol in module.
Unsafe substrate
raw_try_alloc<t>, raw_null<t>, raw_is_null<t>, raw_read<t>,
raw_write<t>, raw_free<t>, raw_slice<t>, atomics, volatile access,
fences, address-space casts, raw assembly, FFI, syscalls, and raw unions
require unsafe. Normal applications should consume safe wrappers from
libraries.
Repository launcher
./seed/seed check [--lib] [path|file.sd]
./seed/seed check [--json|--deep] [--report report.json] [--max-ms n] [--max-rss-kib n] [--max-files n] [--max-loc n] [--lib] [path|file.sd]
./seed/seed build [path] [-o output] [compiler flags]
./seed/seed run [path] [-- program arguments]
./seed/seed test [path] [compiler flags]
./seed/seed format [--check] [file.sd]
./seed/seed fmt [--check] [file.sd] # short alias
./seed/seed fmt --write directory/
./seed/seed fmt --check --json [--output file] [file.sd|directory]
With `--check`, a directory recursively checks its `.sd` files; `.git`,
`target`, and `.seed` directories are ignored.
With `--json`, the formatter emits `seed.format.v1` with per-file status and
stable checked/needs-format/error counts; a non-zero status still includes the
complete JSON result.
./seed/seed new path [--name package_name]
./seed/seed pkg resolve|add|update|prune ...
./seed/seed install [path|package] [--profile release] [--system] [--prefix path] [--dry-run]
./seed/seed uninstall package [--system] [--prefix path] [--dry-run]
./seed/seed doc [path] [--json] [--check-links] [--output file]
./seed/seed lint [--json] [--deny-warnings] [--cache dir] [--no-cache] [--fix] [--output file] [path]
./seed/seed explain [--json] CODE
./seed/seed explain [--json] --list
./seed/seed lsp
./seed/seed repl [--json]
./seed/seed publish --check [--json] [path]
./seed/seed publish --check [path]
./seed/seed mobile android <setup|sign|build|stage|check|package|run|doctor>
check is a narrow direct source check that runs the full semantic pipeline,
including every mandatory safety proof (ownership, provenance, effects,
cleanup, loans, concurrency); reusing a cached HIR does not bypass any of
them. check --deep adds only the didactic lint pass after the semantic
check passes; it never replaces a proof. It resolves relative source/interface
imports and local manifest path dependencies; it does not first compile a
missing bare repository package. Use build or run when validating package
integration that depends on automatic core-library staging.
check --report report.json writes a deterministic seed.check-report.v1
summary with the source, file/LOC counts, deep-mode flag, result, total elapsed
time, and separate source-resolution, semantic-check, and (when requested)
deep-lint phases. On POSIX hosts it also records peak_child_rss_kib for the
compiler/lint subprocesses. If deep lint fails, the report marks both the
deep-lint phase and overall result as fail and includes its error/warning
counts. The report also includes internal compiler phase timings and peak RSS
when the LLVM profiler is available.
--max-ms and --max-rss-kib add explicit CI budgets; they are post-run
checks: the compiler subprocess runs to completion, then the tooling judges
wall time (from tool start) and child peak RSS against the ceilings. Exceeded
budgets are recorded in the report and fail the command after emitting
structured output (budgets.violations plus the distinct check budget exceeded error). --max-files and --max-loc apply the same policy to
reachable source size.
Compiler failures come in two classes. A semantic failure is a program
violation reported as specific diagnostics (codes such as S001, H022) with
spans, notes and help. An infrastructure failure — compiler out-of-memory
or a toolchain contract violation — is reported as error[I-code]: detail
(I900 fallback) or the generic "compiler ran out of memory or violated an
infrastructure contract" message, with no output artifact published. Neither
is the program-side alloc_error, which is a recoverable runtime result type
inside generated programs (see the ownership chapter).
check --json emits the compiler's machine-readable diagnostics plus the
checked source and a stable summary with error/warning counts. It can be
combined with --deep to include the seed.lint.v1 deep-lint payload; add
--deny-warnings to make compiler and deep-lint warnings fail the check while
still emitting the complete JSON envelope. --output file writes that JSON
atomically and keeps stdout clean, including when the check fails.
doc --json emits a seed.doc.v1 documentation index containing public
declarations, rendered Markdown, interface-only dependencies, and structured
link diagnostics. doc --check-links turns unresolved links into a failing
status. lint --deny-warnings similarly turns lint warnings into a failing
status without changing the default warning behavior; lint --output file
writes the JSON or text result atomically, including on a failing lint run.
Lint results are reused from a content-addressed per-source cache under
.seed/cache/lint by default; --cache dir or SEED_LINT_CACHE overrides it,
and --no-cache forces a cold run. --prune-days N removes old cache entries
and reports the count. SEED_BUILD_JOBS controls bounded parallel source
checks while preserving diagnostic order. Cache entries use the
seed.lint-cache.v1 envelope; malformed or older entries are rebuilt and
reported as cache.invalidated. Documentation-only L001 diagnostics are
cached independently per public
declaration, so unrelated edits can reuse them; semantic checking remains
whole-source.
--fix applies only safe documentation-comment fixes and disables cache reuse
for that run.
The compiler's verified-HIR cache can be maintained explicitly with
--cache-prune-days N together with --cache-dir dir. It removes only aged
regular .hci identity files from that directory; unrelated files, symlinks,
temporary publications, and recent entries are preserved. Pruning is
advisory and never bypasses compilation or safety verification.
explain --json CODE emits a seed.explain.v1 record containing the title,
meaning, invalid example, and repair guidance for a diagnostic code. Without
--json, it retains the human-oriented long-form explanation.
explain --list enumerates the available catalog; JSON mode emits
seed.explain-catalog.v1 with the complete structured records.
repl is a check-only line-oriented session. Each entered line is validated
as part of the current fn main region before being accepted. Multiline
regions use :begin and :end transactionally; :check, :show, :reset,
:undo, :help, and :quit manage the session. :show prints only the accepted
region, excluding any open transaction. It does not execute code or weaken ownership
rules.
LSP completion follows the current grammar, including return, loop,
break, and continue; removed legacy spellings are not suggested.
repl --json emits one seed.repl-event.v1 JSON object per event for editor
and automation consumers, including pending transaction lines and session exit.
check --max-files N and check --max-loc N reject projects that exceed
explicit reachable-source scale budgets and record violations in the check
report and JSON diagnostics envelope.
The JSON envelope also exposes observed metrics for reachable files, LOC,
elapsed milliseconds, and peak child RSS.
The result field is explicitly pass or fail, including budget failures.
With --report, JSON mode also embeds the complete phase report while writing
the requested report file atomically.
publish --check --json emits seed.publish-check.v1 with the package,
version, and pass/fail result for CI integrations; --output file writes it
atomically. Validation failures still return a non-zero status after emitting
the structured result. Failure envelopes include stable error_code and the
resolved project, so consumers do not need to parse human error text.
Known classes include PUBLISH_MANIFEST, PUBLISH_LICENSE,
PUBLISH_DEPENDENCY, PUBLISH_PLATFORM, and PUBLISH_FFI_INSTALL.
build performs incremental compilation with automatic dependency resolution.
Bare package imports (e.g. use "vec") are staged into an isolated
target/profile store. A second build reuses matching .sdi/native artifacts.
test forwards trailing compiler flags, including repeated --link-object
and --link-library inputs, to every test binary. This is required for library
tests whose safe Seed surface is backed by an external native ABI.
install compiles the package in release mode and installs it system-wide:
executables to ~/.local/bin (or /usr/local/bin with --system),
dependency generations to ~/.local/lib/seed/generations/, and interfaces
to ~/.local/share/seed/interfaces/. Use --profile debug for debug builds
and --prefix /custom/path for custom install locations.
uninstall removes a previously installed package and its dependency
generations. Both install and uninstall support --dry-run for preview.
The transactional installed-package record includes target, profile,
dependency-generation identity, compiler checksum, installed-artifact
checksum, source checksum and lock checksum so a deployed application can be
traced to its exact build inputs.
Remote publication is separate; seed publish --check validates package and
lock integrity locally without running unrestricted scripts. Compiled packages
use target-qualified .sdi plus --install-library/--install-interface.
Packages declaring extern fn must also provide INSTALL.md describing the
required native setup for their declared platforms; the file must be readable
UTF-8 and non-empty.
Local monorepos can keep multiple independently versioned packages connected by
relative path dependencies. Their tree checksums drive rebuilds; ordinary
local development does not require a Git repository, tag, or publication for
each library.
The launcher reads [build].profile = "debug" | "release" | "release-small"; legacy release = true maps to release. An explicit CLI
profile overrides the manifest. seed install defaults to release regardless
of the manifest unless --profile is explicit. Applications default to src/main.sd; an
optional safe relative [package].entry selects another source within the
package. Bare package imports resolve to matching installed artifacts first;
otherwise they are compiled with the application profile into an isolated
target/profile store. Hosted builds link them dynamically by default. A second
build reuses matching .sdi/native artifacts; private dynamic-library changes
do not rebuild consumers, while public .sdi or ABI changes do.
User installation places executables in ~/.local/bin, immutable dependency
generations in ~/.local/lib/seed, and versioned interface mirrors under
~/.local/share/seed/interfaces. --system uses the platform system scope
(/usr/local on Unix); --prefix overrides the root. Installation and removal
are ownership-recorded and atomic, and no command silently edits shell startup
files or loader environment variables.
Operators
Binary precedence from highest to lowest:
- multiplicative (
*,/,%) - additive (
+,-) - shifts (
<<,>>) - comparisons (
<,<=,>,>=) - equality (
==,!=) - bitwise AND (
&), XOR (^), OR (|) - logical AND (
&&) - logical OR (
||)
&& and || short-circuit: a && b evaluates b only when a is true,
and a || b evaluates b only when a is false. Both operands must be
bool and the result is bool. Short-circuiting also applies to constant
folding, so false && expensive() and true || expensive() are valid constant
expressions even when expensive() is not constant.
Direct clean compiler
The executable is compiler/llvm/build/seed. Its authoritative flag list
is seed --help.
# Source processing
seed --check source.sd
seed --format|--format-check|--format-write source.sd
# Code generation
seed --emit-llvm out.ll source.sd
seed --emit-object out.o source.sd
seed --emit-binary out source.sd
seed --emit-shared library.so source.sd
seed --emit-static-library library.a source.sd
seed --emit-raw out.bin source.sd
seed --run source.sd -- arguments
# Interface inspection
seed --interface-print file.sdi
seed --interface-hash file.sdi
seed --emit-interface out.sdi source.sd
# Library management
seed --install-library artifact --install-interface file.sdi --package name [--seed-home path]
# Debug dumps
seed --lex source.sd
seed --ast-print source.sd
seed --types-print source.sd
seed --symbols-print source.sd
seed --hir-print source.sd
seed --safety-print source.sd
seed --mir-print source.sd
seed --layout-print source.sd
seed --abi-print source.sd
seed --low-mir-print source.sd
seed --llvm-print source.sd
# Build options
seed --release source.sd
seed --release-small source.sd
seed --thin-lto source.sd
seed --pgo-generate dir source.sd
seed --pgo-use profile source.sd
seed --profile-print source.sd
seed --target target source.sd
seed --runtime linux-native|linux-libc source.sd
seed --runtime-profile profile source.sd
seed --runtime-feature feature source.sd
seed --static source.sd
seed --static=all source.sd
seed --sysroot path source.sd
seed --soname name source.sd
seed --entry symbol source.sd
seed --linker-script path source.sd
seed --code-model model source.sd
seed --relocation-model model source.sd
seed --no-red-zone source.sd
seed --link-object file.o source.sd
seed --link-library lib.so|lib.a|package source.sd
seed --cache-dir path source.sd
seed --seed-home path source.sd
seed --package name source.sd
# Diagnostics
seed --diagnostics human|json source.sd
seed --version
seed --help
Debug/inspection modes include token, AST, types, symbols, HIR, safety, MIR,
layout, ABI, low-MIR, and LLVM printing. Target/runtime options include
--target, --runtime-profile, --runtime-feature, --runtime, --sysroot,
entry/linker-script controls, and separate object/library linkage.
--release uses seed-safe-o2-v1; --release-small uses
seed-safe-oz-v1. Both are safe. ThinLTO and PGO are explicit release-only
options; static linkage is independent. There is no operational --unchecked
or --fast-unsafe profile.
Target status
Object support and end-to-end hosted support are separate claims. Linux
x86-64/AArch64 are the closed hosted paths. Freestanding x86-64/AArch64/RISC-V64
have boot evidence at their declared levels. Windows x86-64 has PE/runtime/DLL/
task/network evidence under required Wine. macOS x86-64 and arm64 have
explicit-SDK executable/dylib/static linking, runtime and pthread scheduling.
Native Intel x86-64 execution remains; arm64 is cross-link-only. See
compiler/llvm/tests/GATE14_TARGET_MATRIX.tsv.
On an Intel Mac, run
SEED_MACOS_RUNNER_POLICY=required make -C seed/compiler/llvm gate14-macos-x86_64-test. The script discovers the SDK with xcrun and uses
Apple's ld unless explicitly overridden.