Skip to main content
Sifr enforces static typing at compile time, which means every variable, parameter, and return value has a known type before your program ever runs. The syntax closely mirrors Python’s type annotation style, so if you have written typed Python you will feel right at home — but unlike Python’s runtime type hints, Sifr’s types are mandatory and the compiler rejects programs that violate them.

Type Annotations

Every variable and function signature must carry a type annotation. Sifr infers types in some positions, but being explicit is encouraged and required at function boundaries.

Union Types

Use the | operator to declare that a value can be one of several types. Union types are first-class in Sifr and appear throughout the standard library — for example, dictionary lookups return T | None instead of raising a KeyError.
A missing dictionary key returns None instead of raising an exception. The compiler requires you to handle the None case before using the value as an int.

Type Narrowing

When you test a union value with isinstance or is None, the compiler narrows the type inside that branch. You can then access type-specific fields and methods without any cast.
Narrowing also works across elif chains. Each branch receives only the types that were not eliminated by the preceding conditions.
The compiler tracks which types remain possible at each point in your code. If you reach an else branch after exhausting all union members, the compiler knows which single type must be present.

None Safety

int | None, str | None, and similar optional types are the standard way to represent values that might be absent. The compiler prevents you from using an optional value as its inner type without first checking for None.

Equality Narrowing

The compiler narrows str and other scalar types through equality checks as well. In an if/elif chain comparing a string variable against literal values, each branch receives a narrowed type that excludes already-matched cases.

Collection Truthiness

You can test a collection for emptiness with a plain if not check. The compiler maps this to an efficient empty check on the collection.

Generics and TypeVar

Sifr supports generic functions and classes through TypeVar. Declare a type variable and use it in parameter and return type positions to express relationships between types.
The compiler instantiates a separate, fully-typed version of the function for each concrete type it is called with, so there is no runtime boxing or overhead.

Built-in Scalar Types

Scalar values such as int, float, and bool have value semantics at the source level. Passing them to a function does not create use-after-move friction for the caller.

Learning Paths

Coming from Python?

Translate type hints, missing values, and runtime checks into Sifr’s static model.

Coming from Rust?

Map T | None, Result[T, E], and source-level narrowing back to Rust concepts.