zax.io

A guide to the Zax programming language

View project on GitHub

Zax Programming Language

Optional Types

Optional values are builtin to the language. An optional value either contains a value or contains uninitialized memory. An optional value can be checked if it contains valid data or not and dereferenced when valid data is confirmed to be present inside an optional value.

Declaring an optional type

A single question mark (?) following a type indicates a type’s value may or may not contain valid data. Unlike a pointer, memory for a type’s value is reserved for optional types but the data remains uninitialized until an optional instance is constructed.

MyType :: type {
    value1 : Integer
    value2 : String
}

// `value` is declared as optional and defaults to contain uninitialized
// memory
value : MyType?

Assigning a value is construction

By assigning an optional value to a value accepted by a constructor of the underlying optional value’s type, a new instance of the type is constructed with that value as in input argument.

MyType :: type {
    value1 : Integer
    value2 : String
}

// `valueOptional` is declared as optional and defaults to uninitialized memory
valueOptional : MyType?

value : MyType

// `value` is copy constructed into `valueOptional`'s reserved space and the
// optional value now contains valid data
valueOptional = value

Constructing an optional with no arguments construction

By assigning an optional to an empty constructed type using the multi-value operator [{ }], an optional instance will become constructed with the underlying type’s default constructor in-place. A compiler will optimize the construction into a single construction of a type rather than a default construction follow by another last copy construction for that type.

MyType :: type {
    value1 : Integer
    value2 : String
}

// `valueOptional` is declared as optional and defaults to contain uninitialized
// memory
valueOptional : MyType?

// Using a multi-value declaration (except without any values specified) will
// force the empty constructor of the underlying `type` to be called and
// the underlying type will be constructed in-place.
valueOptional = [{ }]

// Not as optimal: A temporary `MyType` instance is created and `valueOptional`
// is `last` copy constructed into the optional value's reserved space and the
// optional value now contains valid data
valueOptional = # : MyType

// Using a default value type as an assigned value would cause the optional
// value to be reset to its default uninitialized memory state and the
// underlying `type` would not be constructed (and any value currently in the
// optional `type` would be destructed if applicable)
valueOptional = :

// `valueOptional` is reset to contain uninitialized memory (and any value
// currently in the optional `type` would be destructed if applicable)
valueOptional = #

Resetting the optional to uninitialized data

By assigning the optional value to it’s default value (using = #), any contained value contained in the optional value is first destructed (if previously constructed) and then the optional value is reset to contain uninitialized memory. If an optional value did not contain any valid data then a reset of the optional value does nothing.

MyType :: type {
    value1 : Integer
    value2 : String
}

// `valueOptional` is declared as optional and defaults to contain uninitialized
// memory
valueOptional : MyType?

// `value` is copy constructed into `valueOptional` reserved space and the
// optional now contains valid data
valueOptional = value

// `valueOptional` contents are destructed and reset to uninitialized data 
valueOptional = #

Checking if optional contains data

An if statement can be used to check if an optional value contains data in the same manner a pointer can be checked if it points to valid data.

print final : ()(...) = {
    // ...
}

MyType :: type {
    value1 : Integer
    value2 : String
}

// `valueOptional` is declared as optional and defaults to contain uninitialized
// memory
valueOptional : MyType?

if valueOptional        // "false" will print
    print("true")
else
    print("false)

value : MyType

// `value` is copy constructed into `valueOptional` reserved space and the
// optional now contains valid data
valueOptional = value

if valueOptional        // "true" will print
    print("true")
else
    print("false)

Dereferencing the optional

The dot (.) operator can be used to dereference an optional and access the optional value’s contents. Care must be taken to first validate if an optional value contains valid contents otherwise a dereference operation may cause a panic or undefined behavior.

Just like pointers, optional types cannot be implicitly converted to a reference of an underlying type. A programmer must explicitly dereference an optional value so the programmer acknowledges the optional must contain a valid data. A compiler will not enforce a valid value must exist but runtime issues will occur if a programmer dereferences an uninitialized optional value.

MyType :: type {
    value1 : Integer
    value2 : String
}

func final : ()(input : MyType) = {
    // ...
}

// `valueOptional` is declared as optional and defaults to contain
// uninitialized memory
valueOptional : MyType?

// `value` is copy constructed into `valueOptional`'s reserved space and the
// optional now contains valid data
valueOptional = value

// `valueOptional` is dereference, which converts the optional value to a
// reference to the underlying type
func(valueOptional.)

// ERROR: an optional value cannot be implicitly converted to its underlying
// type
func(valueOptional)

// `valueOptional` is declared as optional and defaults to contain uninitialized
// memory
anotherOptional : MyType?

// PANIC: a dereferenced optional type which does not contain valid data can
// cause a panic
func(valueOptional.)

Defaulting an optional value to a constructed state

By assigning an optional value a new value which the underlying type accepts at construction, any previous valid contents inside an optional value are destroyed and then the optional value is reset to a constructed instance using that new value.

MyType :: type {
    value1 : Integer
    value2 : String
}

// `valueOptional` is declared as optional and defaults to contain
// uninitialized memory
valueOptional : MyType?
value : MyType

value.value1 = 5
value.value2 = "beans"

// `value` is copy constructed into `valueOptional`'s reserved space and
// the optional `valueOptional` now contains valid data
valueOptional = value

// ...

value.value1 = 6
value.value2 = "bananas"

// ...

// the previous data within `valueOptional` is automatically destructed;
// `value` is copy constructed again into `valueOptional`'s reserved space;
// the optional `valueOptional` now contains valid data
valueOptional = value

Resetting an optional to a new empty value

By assigning an optional value to empty construction of a type, any previous valid contents inside an optional instance are destroyed and the optional value is reset to a newly constructed instance using the default constructor.

MyType :: type {
    value1 : Integer
    value2 : String
}

// `valueOptional` is declared as optional and defaults to contain uninitialized
// memory
valueOptional : MyType?
value : MyType

value.value1 = 5
value.value2 = "beans"

// `value` is copy constructed into `valueOptional` reserved space and
// the optional `valueOptional` now contains valid data
valueOptional = value

// ...

// previous data within `valueOptional` is automatically destructed;
// optional `valueOptional` constructs the underlying type with the 
// empty constructor using named value construction shorthand with
// no values specified
valueOptional = {}

Resetting to a new value versus dereferencing assignment

Resetting an optional value is not identical to dereferencing an optional value with a dot (.) operator and then assigning the optional value to another value. In the former, any underlying type instance is destructed and a new type instance is constructed. In the latter, a reference to the underlying type instance is obtained and the underlying type’s instance uses its assignment operator to assign new contents to the existing value’s reference.

MyType :: type {
    value1 : Integer
    value2 : String
}

// `valueOptional` is declared as optional and defaults to contain
// uninitialized memory
valueOptional : MyType?
value : MyType

value.value1 = 5
value.value2 = "beans"

// `value` is copy constructed into `valueOptional` reserved space and
// the optional `valueOptional` now contains valid data
valueOptional = value

// ...

value.value1 = 6
value.value2 = "bananas"

// ...

// previous data within `valueOptional` is automatically destructed;
// `value` is copy constructed again into `valueOptional` reserved space;
// the optional `valueOptional` now contains valid data
valueOptional = value

// the contents of `valueOptional` are dereferenced and a reference to the
// underlying type is obtained; the underlying type uses its assignment
// operator to copy the contents from `value`
valueOptional. = value

Optional values can be passed into functions

print final : ()(...) = {
    // ...
}

MyType :: type {
    value1 : Integer
    value2 : String
}

func final : ()(input : MyType?) = {
    // ...

    if input {
        print("found value: ", input.value1, input.value2)
        // ...
    } else {
        print("no value")
    }

    // ...
}

// `valueOptional` is declared as optional and defaults to contain
// uninitialized memory
valueOptional : MyType?
value : MyType

value.value1 = 5
value.value2 = "beans"

// pass in a optional containing uninitialized data
func(valueOptional)     // will print "no value"

// `value` is copy constructed into `valueOptional` reserved space and the
// optional value now contains valid data
valueOptional = value

// pass in a optional containing valid data
func(valueOptional)     // will print "found value: 5beans"

// pass a default instance value for the argument (which is an optional value
// containing uninitialized memory)
func(:)                 // will print "no value"