Clarity Smart Contract Language


Clarity of Mind Foreword Introduction


Data maps are so-called hash tables. It is a kind of data structure that allows you to map keys to specific values. Unlike tuple keys, data map keys are not hard-coded names. They are represented as a specific concrete values. You should use maps if you want to relate data to other data.

A map is defined using define-map:

(define-map map-name key-type value-type)

Both key-type and value-type can be any valid type signature, although tuples are normally used because of their versatility.

;; A map that creates a principal => uint relation.
(define-map balances principal uint)

;; Set the "balance" of the tx-sender to u500.
(map-set balances tx-sender u500)

;; Retrieve the balance.
(print (map-get? balances tx-sender))

Let us take a look at how we can use a map to store and read basic orders by ID. We will use an unsigned integer for the key type and a tuple for the value type. These fictional orders will hold a principal and an amount.

(define-map orders uint {maker: principal, amount: uint})

;; Set two orders.
(map-set orders u0 {maker: tx-sender, amount: u50})
(map-set orders u1 {maker: tx-sender, amount: u120})

;; retrieve order with ID u1.
(print (map-get? orders u1))

It is important to know that maps are not iterable. In other words, you cannot loop through a map and retrieve all values. The only way to access a value in a map is by specifying the right key.

Keys can be as simple or complex as you want them to be:

(define-map highest-bids
    {listing-id: uint, asset: (optional principal)}
    {bid-id: uint}

(map-set highest-bids {listing-id: u5, asset: none} {bid-id: u20})

Whilst tuples make the code more readable, remember that Clarity is interpreted. Using a tuple as a key incurs a higher execution cost than using a primitive type. If your tuple key has only one member, consider using the member type as the map key type directly.

Set and insert

The map-set function will overwrite existing values whilst map-insert will do nothing and return false if the specified key already exists. Entries may also be deleted using map-delete.

(define-map scores principal uint)

;; Insert a value.
(map-insert scores tx-sender u100)

;; This second insert will do nothing because the key already exists.
(map-insert scores tx-sender u200)

;; The score for tx-sender will be u100.
(print (map-get? scores tx-sender))

;; Delete the entry for tx-sender.
(map-delete scores tx-sender)

;; Will return none because the entry got deleted.
(print (map-get? scores tx-sender))

Reading from a map might fail

What we have seen from the previous examples is that map-get? returns an optional type. The reason is that reading from a map fails if the provided key does not exist. When that happens, map-get? returns a none. It also means that if you wish to use the retrieved value, you will have to unwrap it in most cases.

;; A map that creates a string-ascii => uint relation.
(define-map names (string-ascii 34) principal)

;; Point the name "Clarity" to the tx-sender.
(map-set names "Clarity" tx-sender)

;; Retrieve the principal related to the name "Clarity".
(print (map-get? names "Clarity"))

;; Retrieve the principal for a key that does not exist. It will return `none`.
(print (map-get? names "bogus"))

;; Unwrap a value:
(print (unwrap-panic (map-get? names "Clarity")))

The chapter that discusses the different unwrap flavours goes more into what unwrapping means.