Skip to main content

Collection types

Rell supports the following collection types:

  • list<T> - An ordered list that allows duplicate elements.
  • set<T> - An unordered set that does not contain duplicate elements.
  • map<K,V> - A key-value map.

Properties of collection types​

  • Mutable: You can dynamically add or remove elements.
  • Map key or set element: Only non-mutable types can be used as map keys or set elements.

Mutable collection types​

  • Collection types (list, set, map): Always mutable.
  • Nullable type: Only if the underlying type is mutable.
  • Struct type: If the struct has a mutable field or a field of a mutable type.
  • Tuple: If an element type is mutable.

Creating collections​

// list
val l1 = [1, 2, 3, 4, 5, 1];
val l2 = list < integer > ();

// set
val s = set < integer > ();

// map
val m1 = ['Bob': 123, 'Alice': 456];
val m2 = map < text, integer > ();

print(l1); // [1, 2, 3, 4, 5, 1]
print(l2); // []
print(s); // []
print(m1); // {Bob=123, Alice=456}
print(m2); // {}

Combining collections​

Since 0.14.16, the +, -, and & operators combine lists, sets, and maps. The operands are copied (shallow copies, not deep), so for collections of immutable types these operators preserve immutability. Each operator has an equivalent copying function, named to match the existing mutating functions (add_all_copy() is the copying counterpart of add_all()).

Where you would previously write:

val c = list(a);
c.add_all(b);

you can now write:

val c = a + b;
// or
val c = a.add_all_copy(b);
OperatorTypesFunctionResult
a + blista.add_all_copy(b)Concatenation of the two lists.
a - blista.remove_all_copy(b)The elements of a without any element that occurs in b.
a & blista.retain_all_copy(b)The elements found in both a and b.
a + bseta.add_all_copy(b)Union of the two sets.
a - bseta.remove_all_copy(b)The elements of a without any element that occurs in b.
a & bseta.retain_all_copy(b)Intersection of the two sets.
a + bmapa.put_all_copy(b)Merge of the two maps. For keys present in both, the value of b wins.
[1, 2, 3] + [2, 3, 4]; // [1, 2, 3, 2, 3, 4]
[1, 2, 3] - [2, 3, 4]; // [1]
[1, 2, 3] & [2, 3, 4]; // [2, 3]

set([1, 2, 3]) + set([2, 3, 4]); // set([1, 2, 3, 4])
set([1, 2, 3]) - set([2, 3, 4]); // set([1])
set([1, 2, 3]) & set([2, 3, 4]); // set([2, 3])

[1: 'a', 2: 'b'] + [2: 'Z', 3: 'c']; // [1: 'a', 2: 'Z', 3: 'c']

The mutating function collection<T>.retain_all(values) was added at the same time. It removes from the collection any element not contained in the given collection, and returns true if anything was removed.

list<T>​

A list is an ordered collection type that allows duplicate elements.

Constructors​

  • list<T>() - Creates a new empty list.

  • list<T>(list<T>) - Creates a copy of the given list (a list of a subtype is also acceptable).

  • list<T>(set<T>) - Creates a copy of the given set (a set of a subtype is also acceptable).

Functions​

FunctionDescription
.add(T): booleanAdds an element to the end of the list. Always returns true.
.add(pos: integer, T): booleanInserts an element at a given position in the list. Always returns true.
.add_all(list<T>): booleanAdds all elements from the given list to the end of the list.
.add_all(set<T>): booleanAdds all elements from the given set to the end of the list.
.add_all(pos: integer, list<T>): booleanInserts all elements from the given list at the specified position in the list.
.add_all(pos: integer, set<T>): booleanInserts all elements from the given set at the specified position in the list.
.clear()Removes all elements from the list.
.contains(T): booleanChecks if the list contains the specified element.
.contains_all(list<T>): booleanChecks if the list contains all elements from the given list.
.contains_all(set<T>): booleanChecks if the list contains all elements from the given set.
.empty(): booleanReturns true if the list is empty, otherwise returns false.
.index_of(T): integerReturns the position of the first occurrence of the element in the list. Returns -1 if the element isn't found.
.remove(T): booleanRemoves the first occurrence of the specified element from the list. Returns true if found.
.remove_all(list<T>): booleanRemoves all elements from the list that are present in the given list.
.remove_all(set<T>): booleanRemoves all elements from the list that are present in the given set.
.remove_at(pos: integer): TRemoves the element at the given position in the list and returns it.
.repeat(n: integer): list<T>Returns a new list, which is the old list repeated "n" times. Always creates a new list (even if n = 1).
.size(): integerReturns the number of elements in the list.
.reverse()Reverses the order of elements in the list in place. Returns nothing.
.reversed(): list<T>Returns a reversed copy of the list. Always creates a new list (even if the old list is empty or has one element).
._sort()Sorts the list in ascending order. Returns nothing.
.sorted(): list<T>Returns a sorted copy of the list in ascending order.
.to_text(): textReturns a text representation of the list, e.g., '[1, 2, 3, 4, 5]'.
.sub(start: integer[, end: integer]): list<T>Returns a sub-list (start-inclusive, end-exclusive) from the list.

Special operators​

  • [] - Accesses or modifies an element at a specified index in the list.
  • in - Checks if a specified value exists in the list, returning true if it does.

Example:​

struct Transaction {
id: integer;
amount: integer;
sender: text;
recipient: text;
}

function example_list() {
// Creating a list of transactions
val transactions = list<Transaction>();

// Adding transactions to the list
transactions.add(Transaction(id = 1, amount = 100, sender = "Alice", recipient = "Bob"));
transactions.add(Transaction(id = 2, amount = 200, sender = "Bob", recipient = "Charlie"));
transactions.add(Transaction(id = 3, amount = 150, sender = "Charlie", recipient = "Alice"));

// Accessing a transaction by index
val firstTransaction = transactions[0];
print("First transaction amount: %d".format(firstTransaction.amount));

// Modifying a transaction at a specific index
transactions[1] = Transaction(id = 2, amount = 250, sender = "Bob", recipient = "Charlie");

// Using contains with a custom equality check
val searchTransaction = Transaction(id = 3, amount = 150, sender = "Charlie", recipient = "Alice");
if (searchTransaction in transactions) {
print("Transaction found in the list");
}

// Using sub to get a portion of the list
val subList = transactions.sub(0, 2);
print("Sub-list size: %s".format(subList.size()));

// Creating a reversed copy of the list
val reversedList = transactions.reversed();

// Converting the list to text
print("Transactions: %s".format(transactions.to_text()));
}

example_list();

set<T>​

An unordered collection type that does not allow duplicate elements.

struct Address {
value: text;
}

function example_set() {
// Creating a set of unique addresses
val uniqueAddresses = set<Address>();

// Adding addresses to the set
uniqueAddresses.add(Address("0xabc123"));
uniqueAddresses.add(Address("0xdef456"));
uniqueAddresses.add(Address("0xabc123")); // This won't be added as it's a duplicate

// Checking if an address exists in the set
if (Address("0xabc123") in uniqueAddresses) {
print("Address 0xabc123 is in the set");
}

// Removing an address from the set
uniqueAddresses.remove(Address("0xabc123"));

// Adding multiple addresses at once
val newAddresses = [Address("0xghi789"), Address("0xjkl012")];
uniqueAddresses.add_all(newAddresses);

// Printing the set
print("Unique addresses: %s".format(uniqueAddresses.to_text()));
}

example_set();

Constructors​

  • set<T>() - Creates a new empty set.

  • set<T>(set<T>) - Creates a copy of the given set (a set of a subtype is also acceptable).

  • set<T>(list<T>) - Creates a copy of the given list (with duplicates removed).

Functions​

FunctionDescription
.add(T): booleanIf the element isn't in the set, adds it and returns true.
.add_all(list<T>): booleanAdds all elements from the given list to the set. Returns true if at least one element was added.
.add_all(set<T>): booleanAdds all elements from the given set to the set. Returns true if at least one element was added.
.clear()Removes all elements from the set.
.contains(T): booleanChecks if the set contains the specified element.
.contains_all(list<T>): booleanChecks if the set contains all elements from the given list.
.contains_all(set<T>): booleanChecks if the set contains all elements from the given set.
.empty(): booleanReturns true if the set is empty, otherwise returns false.
.remove(T): booleanRemoves the specified element from the set. Returns true if the element was found and removed.
.remove_all(list<T>): booleanRemoves all elements from the set that are present in the given list. Returns true if at least one element was removed.
.remove_all(set<T>): booleanRemoves all elements from the set that are present in the given set. Returns true if at least one element was removed.
.size(): integerReturns the number of elements in the set.
.sorted(): list<T>Returns a sorted copy of the set as a list.
.to_text(): textReturns a text representation of the set, e.g., '[1, 2, 3, 4, 5]'.

Special operators​

  • in - Checks if a specified value exists in the set, returning true if it does.

Example:​

struct Address {
value: text;
}

function example_set() {
// Creating a set of unique addresses
val uniqueAddresses = set<Address>();

// Adding addresses to the set
uniqueAddresses.add(Address("0xabc123"));
uniqueAddresses.add(Address("0xdef456"));
uniqueAddresses.add(Address("0xabc123")); // This won't be added as it's a duplicate

// Using 'in' operator to check if an address exists in the set
if (Address("0xabc123") in uniqueAddresses) { //'in' operator
print("Address 0xabc123 is in the set");
}

// Additional examples using the 'in' operator
val addressesToCheck = [
Address("0xabc123"),
Address("0xdef456"),
Address("0xghi789"),
Address("0xjkl012")
];

for (addr in addressesToCheck) {
if (addr in uniqueAddresses) { //'in' operator
print("Address %s is in the set".format(addr.value));
} else {
print("Address %s is not in the set".format(addr.value));
}
}

// Removing an address from the set
uniqueAddresses.remove(Address("0xabc123"));

// Adding multiple addresses at once
val newAddresses = [Address("0xghi789"), Address("0xjkl012")];
uniqueAddresses.add_all(newAddresses);

// Printing the set
print("Unique addresses: %s".format(uniqueAddresses.to_text()));
}

example_set();

map<K,V>​

A key-value pair collection type.

Basic map example​

function basic_map_example() {
// Creating a simple map with literal values
val person = ["name": "Bob", "sibling": "Alice"];

// Accessing values by key
print("Name: %s".format(person["name"]));
print("Sibling: %s".format(person["sibling"]));
}

basic_map_example();
Key overwriting during instantiation

Rell does not prevent overwriting keys during map instantiation. If you specify the same key multiple times, the last value will be used:

// The second "base_url" value will overwrite the first one
val person = ["name": "Bob", "sibling": "Alice", "sibling": "Michelle"];
// Result: person["sibling"] will be "Michelle"

Complex map example​

struct Balance {
amount: integer;
lastUpdated: timestamp;
}

function example_map() {
// Creating a map of blockchain addresses and their balances
val balances = map<text, Balance>();

// Adding balances to the map
balances["0xabc123"] = Balance(amount = 1000, lastUpdated = 1723472827);
balances["0xdef456"] = Balance(amount = 2000, lastUpdated = 1723472900);

// Accessing a balance by address
val aliceBalance = balances["0xabc123"];
print("Alice's balance: %d, Last updated: %d".format(aliceBalance.amount, aliceBalance.lastUpdated));
}

example_map();

Constructors​

  • map<K,V>() - Creates a new empty map.
  • map<K,V>(iterable<(K,V)>) - Creates a new map from list, set, or map key-value pairs.

Functions​

FunctionDescription
.clear()Removes all key-value pairs from the map.
.contains(K): booleanChecks if the map contains the specified key.
.empty(): booleanReturns true if the map is empty, otherwise returns false.
.get(K): VGet the value associated with the specified key.
.get_or_null(key: K): V?Returns null if the key isn't in the map; otherwise returns the associated value.
.get_or_default(key: K, default_value: V2): V2Returns a default value if the key isn't in the map; otherwise returns the associated value.
.remove_or_null(key: K): V?Removes the specified key from the map. Returns null if the key isn't in the map.
.put(K, V)Adds/replaces a key-value pair in the map.
.keys(): set<K>Returns a copy of keys in the map.
.put_all(map<K, V>)Adds/replaces all key-value pairs from the given map to the map.
.remove(K): VRemoves the key-value pair associated with the specified key from the map.
.size(): integerReturns the number of key-value pairs in the map.
.to_text(): textReturns a text representation of the map, e.g., '{x=123, y=456}'.
.values(): list<V>Returns a copy of values in the map.

Special operators​

  • [] - Accesses or modifies a value by key.
  • in - Checks if a specified key exists in the map, returning true if it does.

Example:​

struct Balance {
mutable amount: integer;
lastUpdated: timestamp;
}

function example_map() {
// Creating a map of blockchain addresses and their balances
val balances = map<text, Balance>();

// Using [] operator to add and modify balances
balances["0xabc123"] = Balance(amount = 1000, lastUpdated = 1723472827);
balances["0xdef456"] = Balance(amount = 2000, lastUpdated = 1723472937);
balances["0xghi789"] = Balance(amount = 3000, lastUpdated = 1723473427);

// Using [] operator to access balances
print("Alice's balance: %d".format(balances[0xabc123].amount));
print("Bob's balance: ${balances[0xdef456].amount}");

// Using [] operator to modify an existing balance
balances["0xabc123"].amount += 500;
print("Alice's updated balance: %d".format(balances[0xabc123].amount));

// Using 'in' operator to check for key existence
val addresses = ["0xabc123", "0xdef456", "0xjkl012", "0xmno345"];
for (address in addresses) {
if (address in balances) {
print("Address %s exists in the map with balance: %d".format(address, balances[address].amount));
} else {
print("Address %s does not exist in the map".format(address));
}
}

// Combining [] and 'in' operators for safe access
val charlieAddress = "0xghi789";
if (charlieAddress in balances) {
val charlieBalance = balances[charlieAddress];
print("Charlie's balance: %d, Last updated: %d".format(charlieBalance.amount, charlieBalance.lastUpdated));
}

// Using [] operator with get_or_default for safe access with a default value
val davidAddress = "0xjkl012";
val davidBalance = if (davidAddress in balances) balances[davidAddress] else Balance(0, block.timestamp);
print("David's balance: %d".format(davidBalance.amount));

// Printing updated balances
for ((address, balance) in balances) {
print("Address: ${address}, Updated Balance: %d".format(balance.amount));
}
}