Home Documentation Templates Examples Showcase GitHub
Theme

Types, optionality, ranges & enums

Nift v4.3 adds runtime type inspection, null propagation/defaulting, compact destructuring, integer ranges and symbolic integer-backed enums. These use the same expression engine in templates, nift run and nift eval.

Runtime type inspection

type(value) returns a stable public type name. Numeric values distinguish int from float; is_number() accepts either.

print(type(null))
@// "null"
print(type(42))
@// "int"
print(type(3.14))
@// "float"
print(type("nift"))
@// "string"

print(is_null(null))
@// true
print(is_number(3.14))
@// true
print(is_int(42))
@// true
print(is_float(3.14))
@// true
FunctionMeaning
type(value)Returns the stable public runtime type.
is_null, is_boolNull and boolean predicates.
is_number, is_int, is_floatNumeric predicates; number is the int/float union.
is_string, is_array, is_objectCore JSON/value predicates.
is_struct, is_function, is_collection, is_enumNift runtime-value predicates.

Nift has no separate public undefined value. Missing object access remains an error; obj.has(key) distinguishes a missing key from a present key containing null.

Null coalescing and safe access

label := post.subtitle ?? "Untitled"
name := user?.profile?.name ?? "Anonymous"
value := maybe_object?[key]

?? evaluates its right-hand side only when the left side is null. ?. and ?[] are null propagation only: a null receiver produces null; a non-null receiver uses ordinary access rules. Missing members, missing keys and invalid receiver types therefore remain errors rather than being silently hidden.

Destructuring

[first, second] := pair
[first, second] = next_pair

{heading, date} := post
{heading, date} = updated_post

v4.3 deliberately keeps destructuring small and predictable. Array patterns are flat and exact-length. Object patterns are flat, require every named key, and ignore unrelated extra keys. Nested patterns, rest elements, defaults and rename syntax are not part of the v4.3 surface.

Integer ranges

print(range(5).join(","))
@// 0,1,2,3,4
print(range(2, 6).join(","))
@// 2,3,4,5
print(range(0, 10, 2).join(","))
@// 0,2,4,6,8
print(range(10, 0, -2).join(","))
@// 10,8,6,4,2

Ranges are stop-exclusive, integer-only and materialize an array. A zero step is an error. A step pointing away from the stop produces an empty array.

Enums

enum Status {
    Draft,
    Published,
    Archived
}

enum HttpStatus {
    OK = 200,
    Created,
    NotFound = 404
}

print(Status.Published.to_int())
@// 1
print(Status.Published.to_string())
@// Published
print(type(Status.Published))
@// enum
print(is_enum(Status.Published))
@// true

In standalone scripts use the native enum Name { ... } form shown above. In template source, the equivalent declaration is @enum(Name){ ... }; declarations inside @script use the native script form.

Implicit numbering starts at 0. Explicit and implicit integer values may be mixed; the next implicit member follows the previous value. Explicit values may be negative, but duplicate integer values and duplicate member names are errors. Enum members are symbolic values rather than arbitrary integers: use to_int() when the backing integer is required and to_string() for the member name. Arbitrary string/float/object/array backing values are intentionally not supported.

Enum members compose inside object literals (whose values are ordinary Nift expressions) and remain symbolic in rendering while serializing as their backing integer:

enum Status { Draft, Published }

post := {"title": "Hello", "status": Status.Published}
print(post.status)          @// Published
print(post.status.to_int()) @// 1
print(post.stringify())     @// {"title":"Hello","status":1}