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: