Skip to main content

Gno Data Structures

Gno supports the same basic data structures as Go. This guide covers how to use them in your realms and packages.

Quick Reference

TypeExampleBest For
Array[5]intFixed-size collections
Tree-backed indexavl.Tree, bptree.BPTreeLarge/growing sorted datasets
Mapmap[string]intSmall key-value stores
Slice[]stringDynamic lists
Structtype User struct{...}Grouped data
Pointer*UserReference values

Arrays

Fixed-size collections where the size is set at compile time.

var numbers [5]int
primes := [5]int{2, 3, 5, 7, 11}

// Access elements
numbers[0] = 1
value := numbers[0]

Arrays are copied when passed to functions. Use pointers to modify them: func update(arr *[5]int).

Tree-backed Indexes

For large or growing sorted datasets, prefer a tree-backed index over a persistent map. Tree implementations store nodes or leaf pages separately, so reading or writing one key does not require loading the whole collection.

Common choices include:

TypeGood fitTradeoffs
avl.TreeGeneral sorted key/value indexes, range scans, offset paginationO(log n) lookup, values are any, keys are strings
bptree.BPTreeLarge sorted indexes where higher fanout and fewer pointer dereferences helpMore tuning surface; choose fanout intentionally when the default is not enough
avl or bptree/listList-like APIs backed by tree storageStill design keys and pagination around your product
import "gno.land/p/nt/avl/v0"

var users avl.Tree

// Set
users.Set("alice", User{Score: 100})

// Get (returns nil if key not found)
value := users.Get("alice")
if value != nil {
user := value.(User) // Type cast required
}

// Has (check if a key exists without retrieving the value)
// Note: a key stored with a nil value is indistinguishable from an
// absent key when using Get; use Has to check for existence.
if users.Has("alice") {
// key exists
}

// Iterate in sorted order
users.Iterate("", "", func(key string, value any) bool {
user := value.(User)
println(key, user.Score)
return false // continue iterating
})

Use an explicit ID helper for append-like records. seqid.ID generates keys that preserve numeric order when stored in a tree:

import (
"gno.land/p/nt/avl/v0"
"gno.land/p/nt/seqid/v0"
)

var (
nextPostID seqid.ID
posts avl.Tree // seqid string -> Post
)

func AddPost(post Post) string {
id := nextPostID.Next().String()
posts.Set(id, post)
return id
}

Add secondary indexes for each lookup path users need:

var (
postsByID avl.Tree // id -> Post
postsByAuthor avl.Tree // author + "/" + id -> id
)

Learn more: Effective Gno: Choose storage types by access pattern

For non-official storage helpers such as unique lists, sets, and queues, see Community Packages.

Maps

Key-value stores with O(1) lookup.

scores := make(map[string]int)

// Set
scores["alice"] = 100

// Get with existence check
score, exists := scores["alice"]

// Delete
delete(scores, "alice")

// Iterate
for username, score := range scores {
println(username, score)
}

Note: In Gno, map iteration order follows insertion order, unlike Go which uses randomized iteration order due to underlying C hashmap implementation. While this makes Gno behavior deterministic, you should still not rely on iteration order for correctness or public Render output. Use an explicit ordered list or tree-backed index when users depend on stable ordering, pagination, or range queries.

Slices

Dynamic, growable lists.

var users []string
users = append(users, "alice")
users = append(users, "bob")

// Iterate
for i, user := range users {
println(i, user)
}

// Remove element at index
users = append(users[:index], users[index+1:]...)

Pre-allocate capacity for better performance: make([]string, 0, 100).

Slices hold references to underlying backing arrays; so modifying a value in a slice is like modifying it in a pointer to an array. This is particularly relevant for cross-realm interactions: the elements of a slice of another realm are references, not values.

To copy a slice, use append on a nil slice:

users = append([]string(nil), otherSlice...)

Structs

Group related data together.

type User struct {
Name string
Score int
}

// Create
user := User{Name: "alice", Score: 100}

// Methods with pointer receiver (can modify)
func (u *User) IncrementScore() {
u.Score++
}

// Methods with value receiver (read-only)
func (u User) Display() string {
return u.Name + ": " + strconv.Itoa(u.Score)
}

Pointers

Reference values instead of copying them.

x := 42
ptr := &x // Get address
*ptr = 100 // Modify through pointer (x is now 100)

// With structs
func UpdateScore(u *User, score int) {
u.Score = score // Modifies original
}

user := &User{Name: "alice"}
UpdateScore(user, 100)

Always check for nil: if ptr == nil { return }.

Persistence in Realms

Global variables in realms are automatically saved between transactions. This is a key feature of Gno's automatic state management.

var (
counter int // Single value
users []string // Entire slice
scores map[string]int // Entire map
tree avl.Tree // Only modified nodes
)

Learn more: