Language reference
Differences from Python
Nox looks like Python and most small Python programs become Nox programs by adding type annotations. This page is the checklist of what differs, grouped by what you will hit first. Everything here is deliberate unless marked (not yet).
Types are mandatory#
Every variable's first assignment, every parameter and every return value carries a type. There is no Any, no dynamic attribute
creation, no setattr/getattr/exec/eval. x = [] is an error — write x: list[int] = []. A name keeps its type for its scope.
Numbers#
| Python | Nox |
|---|---|
unbounded int |
64-bit int that wraps on overflow; fixed-width types (u8…i64) trap on overflow |
2 ** -1 is 0.5 |
int ** int is an int (negative exponent → 0); use a float base |
10 ** 30 is exact |
wraps modulo 2⁶⁴ |
True + 1 is 2 |
bool never takes part in arithmetic — int(True) + 1 |
if x: truthiness |
conditions must be bool — write if x != 0: / if len(xs) > 0: |
round(2.5) is 2 |
same (half to even), but round(x) always returns int |
int(u8_value) n/a |
int(x) works on fixed-width integers and bool |
Integer // and % by zero raise ZeroDivisionError; float division never raises (inf/nan). Floats print like Python's repr.
Syntax#
- No semicolons, no one-line compound statements (
if x: y()must be two lines), no walrus:=, nolambdawith statements. - No
*args/**kwargs, no keyword-only (*) or positional-only (/) markers, no argument unpacking at call sites, no star-assignment (a, *b = xs). Default values must be constant literals. - No
yield/generators/iterators; generator expressions are accepted only as a call argument and are evaluated eagerly into a list. - No
global/nonlocal: module-level names are assigned directly from functions; a nested function may only read captured variables. - No raw strings (
r"…"), no adjacent-literal concatenation, no\x/\Uescapes, no%formatting, no!rin f-strings. ASCII-only identifiers. - No
try … else, nofor … else/while … else, no bareraise(useraise e), noraise … from …, no multi-itemwith. - No
async with/async for/awaiton arbitrary expressions:awaittakes aTaskor a channel operation. - No
del name(onlydel xs[i]anddel d[k]), nopass-only protocol bodies other thanpassitself. __name__ == "__main__"does not exist: the top-level statements of the entry file are the program, andmainis a reserved name.- Python's
matchstatement,typealiases, decorators that wrap,@property,@staticmethodand@classmethodare not part of Nox.
Collections and iteration#
map,filter,zip,enumerateandreversedreturn new lists, not lazy iterators;rangeis only usable infor, comprehensions andlist(...).dictkeys areint,float,boolorstr; values are scalars, classes, lists or dicts.setelements areint,float,boolorstr. Both keep insertion order (as Python dicts do; Python sets do not). Tuples cannot be dictionary keys.- Lists are homogeneous:
[1, "a"]is a type error. A list of subclasses needs an annotation (xs: list[Base] = [Dog(), Cat()]). - Slices are values — you cannot assign to a slice or delete one.
xs.sort()needsint,floatorstrelements unless you passkey=. - A loop variable keeps its value after the loop, but after a
rangeloop it is one past the last element, not the last element. sorted,min,maxtakekey=, but the elements (or the keys) must beint,floatorstr.
Strings#
str is UTF-8 and indexed by code point; case conversion and whitespace classification are ASCII-only ("é".upper() is unchanged). The format
mini-language is fully supported in f-strings, format() and str.format.
Classes#
- Single inheritance only; no metaclasses,
__slots__,__getattr__,__call__, class-level attributes orisinstance. - Fields are fixed: created in
__init__(or declared in the class body) and never added later.selfmay be left unannotated. ==on two instances compares fields structurally unless you define__eq__;isis only forNone.- Operator overloading uses the usual dunder names;
x += ycalls__add__. - A class used before it is defined is fine; a protocol is structural and only usable as a parameter type (no
list[Protocol]).
Errors and resources#
Exceptions are classes, except Base: catches subclasses, and e.message/e.line are available. There is no traceback object — an uncaught
exception prints the class and source line. Resources are released by with, defer and finally; there is no __del__ and no weakref.
The text of built-in errors is currently Turkish.
Modules#
import a.b / from a.b import x [as y] only — no import *, no relative imports, no __all__, no dynamic import. All modules compile together;
module-level variables are read from other modules with from m import name, not as m.name. Third-party code arrives through nox.json and
noxc fetch, not pip.
Concurrency#
There is no GIL and no threading/asyncio. Tasks (spawn/await), channels and nox.thread replace them, and on the default backend tasks run
in parallel on a worker pool (Concurrency). There is no list[Task[T]]; gather results through a channel.
Performance model#
Nox is compiled ahead of time to native code. There is no interpreter overhead, no boxing of int/float, and memory is released
deterministically. A straight port of a numeric Python loop typically runs one to two orders of magnitude faster.