Composite types
These are more complex types that contain a number of other types. Composites make it a lot easier to create larger smart contracts.
Optionals
The type system in Clarity does not allow for empty values. It means that a
boolean is always either true
or false
, and an integer always contains a
number. But sometimes you want to be able to express a variable that could have
some value, or nothing. For this you use the optional
type. This type
wraps a different type and can either be none
or a value of that type. The
optional type is very powerful and the tooling will perform checks to make sure
they are handled properly in the code. Let us look at a few examples.
Wrapping a uint
:
(some u5)
An ASCII string:
(some "An optional containing a string.")
Or even a principal:
(some 'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE)
Nothing is represented by the keyword none
:
none
Functions that might or might not return a value tend to return an optional
type. As we saw in the previous section, both element-at
and index-of
returned a (some ...)
. It is because for some inputs, no matching value can be
found. We can take the same list but this time try to retrieve an element at an
index larger than the total size of the list. We see that it results in a
none
value.
(element-at (list 4 8 15 16 23 42) u5000)
When writing smart contracts, the developer must handle cases where (some ...)
is returned differently from when none
is returned.
In order to access the value contained within an optional, you have to unwrap it.
(unwrap-panic (some u10))
Trying to unwrap a none
will result in an error because there is nothing to
unwrap. The "panic" in unwrap-panic
should give that away.
(unwrap-panic none)
Later chapters on error handling and defining custom functions will dive into how to deal with such errors and what effects they have on the chain state.
Tuples
Tuples are records that hold multiple values in named fields. Each field has its own type, making it very useful to pass along structured data in one go. Tuples have their own special formatting and use curly braces.
{
id: u5, ;; a uint
username: "ClarityIsAwesome", ;; an ASCI string
address: 'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE ;; and a principal
}
The members inside tuples are unordered. You retrieve them by name and cannot
iterate over them. A specific member can be read using the get
function.
(get username { id: 5, username: "ClarityIsAwesome" })
Tuples, like other values, are immutable once defined. It means that they cannot be changed. You can, however, merge two tuples together to form a new tuple. Merging is done from left to right and will overwrite values with the same key.
(merge
{id: 5, score: 10, username: "ClarityIsAwesome"}
{score: 20, winner: true}
)
The above expression will result in a tuple with both keys merged and the score
set to 20
.
Since the merge function returns an entirely new tuple, member types can in fact be overwritten by a later tuple in the sequence.
(merge
{id: u6, score: 10}
{score: u50}
)
Responses
A response is a composite type that wraps another type just like an optional. What is different, however, is that a response type includes an indication of whether a specific action was successful or a failure. Responses have special effects when returned by public functions. We will cover those effects in the chapter on functions.
A response takes the concrete form of either (ok ...)
or (err ...)
. Wrapping
a value in a concrete response is straightforward:
(ok true)
Developers usually come up with their own rules to indicate error status. You could for example use unsigned integers to represent a specific error code.
(err u5) ;; something went wrong.
There are no explicit rules on which types you should wrap for your responses. Standards are currently being proposed and we will touch upon a few in the chapter on Stacks Improvement Proposals (SIPs).
Responses can be unwrapped in the same way as optional types:
(unwrap-panic (ok true))
Although not necessary, private functions and read-only functions may also return a response type.