Informative translation; the Russian text is normative.
Russian original (normative): overview.md
Nova — overview
Central idea
Network, disk, time, randomness, logging, errors, mutation — in Nova these
are all effects. A function declares in its signature the effects it
uses itself; calls to other functions do not pull those functions’ effects
up into the caller’s signature (the exception is Fail — errors are visible
transitively). Each effect has a handler that intercepts its operations.
Everything else in the language follows from a single abstraction (algebraic effects in the Koka/Effekt style, brought to a production-ready state). See revolutionary.md for the full treatment.
effect vs protocol
Nova has two different ways to describe “something with operations”:
- “How to do something” — a function declares that it needs certain
operations, and which implementation sits underneath them is decided by
the calling code via a
with-block (for production — Postgres, for tests — in-memory). This is an effect, declared viatype X effect { ... }. - “What a value can do” — the implementation is tightly bound to the
type:
inthashes this way,strthat way, and that cannot be changed. This is a protocol, declared viatype X protocol { ... }.
When to use an effect and when a protocol in code: if you want a different implementation for testing — it is an effect. If testing just means working with values of the type and there is nothing to substitute — it is a protocol.
Killer use-case
AI-first programming. When an LLM writes 50–80% of the code, the language needs:
- side effects visible in the signature (effects)
- compile-time guarantees instead of runtime checks (contracts, capabilities)
- context locality (one function understandable without reading 10 files)
- compiler errors as a learning signal for the LLM
- syntax stability (LLMs learn from old data)
All existing languages were designed before the AI era. Nova is the first language explicitly optimized for the pair “LLM writes, human reviews”.
Supporting decisions
-
Compiled ONLY through a C backend (AOT), like Go/Rust. The early idea of “one source, three execution modes” (AOT/JIT/interpreter) is not implemented:
nova run file.nv(tree-walking interpreter) is retracted — the command remains in the CLI only as a stub that clearly reports this and points tonova build/nova test(seedocs/dev/read-project.md). Code is tested and shipped only through C-codegen too — there is no separate “interpreted” path with different semantics. -
Memory: managed by default (current: Boehm conservative GC; v1.0+: concurrent GC), regions opt-in for real-time. The programmer writes code without memory prefixes — allocations are freed automatically. Current state of the bootstrap runtime (Plan 27, default since 2026-05-11): Boehm GC, measured pauses (see
nova_tests/concurrency/gc_pause_bench.nv) on x86_64-v3 Windows debug-build:- 10k objects × 20 rounds: max < 16ms, p99 ≈ avg ≈ 0ms (within the tick of GetTickCount64 — Windows timer gran 15.6ms).
- 100k objects × 10 rounds: max < 16ms.
- 1M objects × 3 rounds: max < 16ms.
These are upper bounds via a low-res timer; real pauses are most likely smaller. Hi-res measurement (uv_hrtime) is a separate task after bootstrap.
Design goal for v1.0+: concurrent GC, p99 < 1ms on typical workloads (decisions/05-memory.md#d6, Plan 25 G3b).
Escape analysis keeps everything that does not escape on the stack (no GC overhead). For real-time zones (audio, trading, embedded) there is the
#realtime nogc fnattribute (D172 §7; historically therealtime nogc { }block, D64, retracted in Plan 113), combinable withregion { }for arena allocations (⚠regionis not implemented in the current compiler).Introspection API (Plan 32):
gc.heap_size(),gc.collect(),gc.live_count()available without import. -
Structural typing + type inference everywhere.
-
Protocols + data instead of classes. No inheritance. Structural contracts via
protocol(see decisions/01-philosophy.md#d1, decisions/02-types.md#d42). -
Contracts in the signature.
requires/ensures/invariant— optional, but statically checked where possible. -
Structured concurrency on top of an M:N scheduler (runtime codename — Vela).
spawn/supervised/detach/cancel tokens — the same fibers (mco-coroutines) that carry the async/await infrastructure from the section above.main()itself runs as a fiber (D92, retraction of Rule 6, 2026-07-25) — blocking park/wake operations (Time.sleep,TcpListener.accept(),Channel.recv()) are legal directly inmain(), without wrapping them insupervised { spawn { … } }. The same holds directly in the body ofsupervised { … }without an intermediatespawn(D439, 2026-07-30) — but such a direct blocking operation is NOT protected by thetimeout:/cancel:of that scope (enforcement lives only in the join loop, which starts after the body’s statements); to be protected by a deadline/token you needspawn { … }. Failure supervision is an ordinarySupervisoreffect (D416): ready-made policiesescalate()/stop(), custom ones — a handler literalon_child_fail(idx, err) -> Decision. Note: theon_child_failserialization documented in D416§2 on the runtime’s drive fiber is NOT yet guaranteed (disproved by measurement on 2026-07-31, registry №173) — mut-captures in such a handler are checked by the D441 enforcer like in any other, no exception. Naming conventions for this layer —docs/dev/mn-coding-conventions.md. Memory model between fibers (D415, D441, 2026-07-31): amut-capture is a linear resource of a single fiber; it can cross the boundary (spawn/detach/parallel for/channel/with-handler around a fiber-containing body) only via an explicit move (consume), aro-view, or a value from the whitelist of synchronized types (Atomic*,Mutex, channel ends,#share-types) — checked transitively, including when a closure crosses the boundary as DATA (as a parameter/via a channel), not only on a direct syntactic capture. The same covers a precomputed handler (ro h = |..| {..}thenwith X = h { … }) and the transitive installation of a handler by parameter (A-V10, D441 §5, 2026-07-31). A separate axis is#thread_affine extern fn(A-V10, D441 §5): marks an M:N-unsafe C-side list (thread-local state), raised transitively along the call graph, gated at thespawn/detach/parallel forboundary.
What it borrows from whom
| Feature | Source |
|---|---|
| Algebraic effects + handlers | Koka, Effekt, Eff |
| Compilation speed, simple syntax | Go |
| Performance, traits, monomorphization | Rust |
| Concurrent GC, memory simplicity for backends | Go, Java ZGC |
| Pattern matching, ADT, sum types | OCaml/Rust |
| Memory regions | Zig, Odin |
| Structured concurrency, supervision | Erlang/OTP, Swift |
| Contracts, refinement types | Eiffel, Dafny, F* |
| Capability security | E, Pony |
Tooling out of the box
Today — implemented in the nova CLI (nova-cli/):
nova build file.nv— a static binary via the C backend (the only execution path, see “Supporting decisions” item 1)nova check [paths]— typecheck + lint without building (--strict-effects— Plan 197, transitive effects as hard errors;--lint— the same convention rules asnova lint)nova test [filter]— discovery + parallel run of.nvtests (C-codegen pipeline; structured errors with EXPECT markers for negative tests, D89)nova lint— a registry of conventionalW_*rules (Plan 185), info mode by default,--deny— a CI gatenova doc file.nv [--format markdown|json|html]— documentation generator: doc-tests (--test), coverage (--coverage[-threshold]), watch mode, mutation-testing for contracts (--mutate-contracts) — Plan 45, CLOSEDnova bench file.nv— running benchmarks (release mode, samples, regression gate) — Plan 57nova add/nova update/nova info— dependency management (git/path dependencies +nova.lock.toml;nova info --diff— the effect-surface diff of a package’s public API as a supply-chain gate) — Plan 03.1-03.4. Download proxy (NOVA_PKG_PROXY/nova.override.toml[net] proxy/~/.nova/config.toml) — Plan 233nova regen-runtime [--check]— regeneration ofstd/runtime/*.nvstubs fromruntime_registry.rs(Plan 13)nova daemon start/stop/status— a resident build daemon (only a latency optimization for repeatednova build, behavior byte-identical without it) — Plan 219- LSP (
nova-lsp/) — completion/hover/diagnostics/goto/rename, fully implemented (Plan 104.10, “V2 production”, CLOSED 2026-07-04); development conventions —docs/dev/lsp-conventions.md nova run file.nv— NOT supported: the command remains in the CLI only as a clear error (“usenova build/nova test”), the interpreter itself (treewalk) is not maintained
Roadmap (not implemented):
nova fmtnova check --fragment '...'— typechecking a single function without a project- A content-addressed package manager (like Deno + Nix) — today’s
nova add/updateis simpler (git/path dependencies + lockfile), no content-addressed storage has been built - Hot reload in dev mode
- AI-friendly patches in diagnostics (for LLMs) interpreter that was retracted; the current form of this idea (if it is still alive) is not described in any known plan as of this revision — I am not inventing a new command. —>
Ecosystem (separate repositories)
The language core (this repository) is deliberately narrow; application layers live as separate packages/repositories on top of it:
nova-http— a byte-oriented HTTP transport (client/server on top ofstd/net).- Polaris (
nova-polaris) — a web framework on top ofnova-http, Axum/FastAPI model (Router/Handler/Middleware/extractors); in development, full EN+RU documentation — a separate plan (229). nova-tls— TLS on top ofstd/net, vendored C +.nvfacade, no Rust in the runtime path.
What is thrown out of ordinary languages
- Header files, namespaces, modules-vs-packages — one file = one module
- Null — only
Option[T] - Exceptions as invisible control flow — only the
Fail[E]effect async/awaitkeywords — suspension is ambient runtime (D62), effects in types:Net,Io,Db- Operator overloading on arbitrary types
- Macros as a preprocessor — only typed comptime (like Zig)
- Global mutable state —
mutfields/parameters (locally) or specialized state effects (Counter, Cache) - DI via reflection — dependencies in effects or parameters
- Mock libraries — handlers from the language
- Hidden imports — every identifier is visible from everywhere
Reserved identifiers
Besides the grammar keywords (fn, type, effect, handler, let,
if, match, return, … — about 38 words), Nova has
identifiers with reserved semantics. They are parsed like ordinary names,
but the compiler knows their special meaning in certain contexts.
| Identifier | Category | Where valid | See |
|---|---|---|---|
Self | referential type | in any type context — refers to the receiver type of a method / the type satisfying a protocol | D66 |
any | top type | everywhere; runtime type-tag for downcasts | D54 |
never | bottom type | return type of non-returning functions (throw, panic, loop) | D26 |
Option[T], Some, None | sum type in the prelude | everywhere | D26 |
Result[T, E], Ok, Err | sum type in the prelude | everywhere | D26 |
Error | record type in the prelude | for throw err | D26 |
RuntimeError | sum type in the prelude | bottom-level runtime errors | D26 |
RuntimeNoneError | unit type in the prelude | thrown via expr!! on Option | D85 |
Effect[E, IRT] | first-class type of a handler for effect E with interrupt-VAL type IRT (default never via D88); sugar Effect[E] ≡ Effect[E, never] | everywhere | D61, D87, D88 |
Fail[E], Fail | standard effect | in effect-row signatures | D25, D65 |
Io, Net, Db, Fs, Time, Random, Log, Trace, Ask[T], Alloc[R], Detach, Blocking | standard effects | in effect-row signatures | D2 (REVISED), D50 |
int, i8-i64, u8-u64, f32, f64, str, bool, byte | primitive types | everywhere | D44, D27 |
These identifiers can be locally overridden (for example, a Net type
from a user library), but that is an anti-pattern. The linter will emit
a warning.
What a release freezes
A tag freezes exactly what carries #stable(since = "...") — nothing
more (D460).
| the interface | the promise |
|---|---|
marked #stable(since = "0.1") | shape and observable behaviour are fixed; a change needs a new major release and a spec entry |
| not marked | works, but is not frozen — it may change in the next version without migration |
The rule applies the same way to std, to packages, and to compiler
flags: the promise comes from the mark, not from the fact that something
exists and appears to work.
This does not license shipping broken things. Anything unfrozen must be either working, or named as not working in the release notes. Silently wrong behaviour stays a release blocker whether or not the mark is there — precisely because it deceives the reader who was not counting on stability in the first place.
Main trade-offs
- Algebraic effects are hard to implement — this is the cutting edge of PL, Koka has been around for 10+ years and is still academic.
- Understanding effects is a threshold — solved only by the quality of compiler messages. If they are academically precise and humanly incomprehensible — the language is dead.
- The performance of effects requires aggressive optimization (static handler resolution, inlining).
- Betting on AI coding as the dominant trend is statistically likely, but not guaranteed.
- 9 out of 10 such projects fail. That is the normal risk of a revolutionary attempt. The alternative — a guaranteed “yet another Nim”.