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 | Function | Meaning |
|---|---|
type(value) | Returns the stable public runtime type. |
is_null, is_bool | Null and boolean predicates. |
is_number, is_int, is_float | Numeric predicates; number is the int/float union. |
is_string, is_array, is_object | Core JSON/value predicates. |
is_struct, is_function, is_collection, is_enum | Nift 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}