Optional Types

Making it easier to work with shaders


Optional Types

An Optional<T> value either holds a value of type T or holds no value. The absence of a value is represented by the none literal.

Declaration Syntax

Optional<T> varName;             // default-initialized to none
Optional<T> varName = expr;      // initialized with a value of type T
Optional<T> varName = none;      // explicitly initialized to none

Parameters

  • T is the value type. T may be any Slang type including interface types and generics.

Members

Member Type Description
hasValue bool true when the optional holds a value; false when it is none.
value T The held value. Accessing value when hasValue is false is undefined behavior.

The none Literal

The built-in keyword none represents the absent value. none is implicitly convertible to any Optional<T>.

Optional<int> a = none;    // no value
bool absent = !a.hasValue; // true

Implicit Coercions

Three implicit coercions are defined for Optional<T>:

Value to Optional

Any value of type T is implicitly convertible to Optional<T>. The resulting optional holds the value and hasValue is true.

int x = 42;
Optional<int> opt = x;   // opt.hasValue == true, opt.value == 42

This applies in function call arguments, return statements, and initializers.

none to Optional

none is implicitly convertible to any Optional<T> with hasValue == false.

Optional<float> f = none;  // f.hasValue == false

Optional to Optional coercion

Optional<T> is implicitly convertible to Optional<U> when T is implicitly convertible to U. The coercion preserves the hasValue state: if the source is none, the result is none; if the source holds a value, the inner value is converted from T to U and the result holds that converted value.

Optional<int>   src = 7;
Optional<float> dst = src;  // dst.hasValue == true, dst.value == 7.0

Optional<int>   empty = none;
Optional<float> emptyF = empty;  // emptyF.hasValue == false

This applies to all coercible inner types, including numeric promotions and concrete-to-interface upcasts:

interface IShape { int area(); }
struct Square : IShape { int side; int area() { return side * side; } }

Optional<Square>  sq = Square{ 3 };
Optional<IShape>  sh = sq;  // sh.hasValue == true, sh.value.area() == 9

Comparison with none

An Optional<T> value may be compared to none using == and !=:

Optional<int> opt = 5;
if (opt != none)
    printf("%d\n", opt.value);  // prints 5

This is equivalent to testing opt.hasValue.

if (let ...) Syntax

The if (let name = expr) syntax unwraps an Optional<T> in a single step. The body executes only when expr has a value; inside the body, name is bound to the unwrapped value of type T.

Optional<int> getVal() { ... }

void example()
{
    if (let x = getVal())
    {
        // x has type int here
        printf("%d\n", x);
    }
}

Memory Layout

Optional<T> is lowered to a struct with two fields:

struct Optional<T> { T value; bool hasValue; }

The layout follows the same rules as a struct with those two members. The value field is always present in memory regardless of hasValue; reading value when hasValue is false is undefined behavior.

📝 Remark: The layout is target-specific and subject to standard struct alignment rules. Do not rely on a specific byte layout for serialization.

Restrictions

  • Optional<T> cannot be used as the element type of a resource (e.g., StructuredBuffer<Optional<T>> is not supported).
  • Accessing .value when .hasValue is false is undefined behavior.