Public State
Mappings
There are several functions available to query and modify mappings. The examples below will reference the following mapping:
mapping balance: address => u64;
Querying
To simply check if a value has been set for a particular address in balance:
balance.contains(addr);
Mapping::contains(balance, addr); // Alternate syntax
To query a value for a particular address in balance:
balance.get(addr);
Mapping::get(balance, addr); // Alternate syntax
Note that if value at addr does not exist above, then the program will fail to execute. To query a value with a fallback for this case:
balance.get_or_use(addr, fallback_value);
Mapping::get_or_use(balance, addr, fallback_value); // Alternate syntax
A program can also query values from another program's mappings:
let balance1 = credits.aleo::account.get(addr);
let balance2 = credits.aleo::account.get_or_use(addr, 0u64);
Although values can be queried, a program cannot directly modify another program's mappings.
Modifying
To set a value for a particular address in balance:
balance.set(addr, value);
Mapping::set(balance, addr, value); // Alternate syntax
To remove the value set at particular address in balance:
balance.remove(addr);
Mapping::remove(balance, addr); // Alternate syntax
Usage
fn dubble() -> Final {
let addr: address = self.caller;
return final {
let current_value: u64 = balance.get_or_use(addr, 0u64);
balance.set(addr, current_value + 1u64);
let next_current_value: u64 = balance.get(addr);
balance.set(addr, next_current_value + 1u64);
};
}
Mapping operations are only allowed inside a final { } block or inside a final fn.
Storage Variables
Storage variables behave similar to option types. There are several functions available to query and modify singleton storage variables. The examples below will reference the following:
storage counter: u64;
Initial State
Singleton storage variables are uninitialized when a program is first deployed — declaring storage counter: u32; does not give counter a value. The variable becomes defined the first time something writes to it (counter = 1u32; inside a final { } block) and stays defined thereafter unless explicitly unset (counter = none;).
Reading an uninitialized singleton with .unwrap() halts at runtime; reading with .unwrap_or(default) returns the supplied default. The two common patterns are:
-
Initialize in the
constructor. Theconstructorruns once at deploy time and may set storage variables, so every read after deployment sees a defined starting value:program storage_init.aleo {storage counter: u32;fn read() -> Final {return final {let c: u32 = counter.unwrap();};}@customconstructor() {// Only initialize on the first deployment (edition 0). On upgrade// (edition > 0) the existing storage is preserved.if self.edition == 0u16 {counter = 0u32;}}} -
Use
unwrap_oreverywhere. Treat absence as a normal case and pass an explicit default at every read site.
There is no implicit zero — Leo never substitutes a default value for an absent singleton. Choose one of the patterns above before deployment.
Querying
To query the value currently stored at counter:
let cur1: u64 = counter.unwrap();
Note that if counter has not been initialized, then the program will fail to execute. To query the value with a fallback for this case:
let cur2: u64 = counter.unwrap_or(fallback_value);
Modifying
To set a value for counter:
counter = 5u64;
To unset the value at counter:
counter = none;
Usage
fn increment() -> Final {
return final {
let current_value: u64 = counter.unwrap_or(0u64);
counter = current_value + 1u64;
};
}
Storage variable operations are only allowed inside a final { } block or inside a final fn.
External Access
Storage variables defined in another program can be accessed using the fully qualified form program_name.aleo::storage_name. External storage variables are read-only and cannot be modified.
For example, suppose another program defines the following storage variable:
program external_program.aleo {
storage counter: u64;
fn touch() -> Final {
return final {};
}
@noupgrade
constructor() {}
}
You may query this value from your program using:
let value: u64 = external_program.aleo::counter.unwrap();
As with local storage variables, calling unwrap() will cause execution to fail if the storage variable has not been initialized. To safely query the value with a fallback:
let value2: u64 = external_program.aleo::counter.unwrap_or(0u64);
External storage variables cannot be assigned to or unset. The following operations are invalid:
external_program.aleo::counter = 5u64; // invalid
external_program.aleo::counter = none; // invalid
Storage Vectors
Storage vectors behave like dynamic arrays of values of a given type. Several functions are available to query and modify storage vectors. The examples below reference the following declaration:
storage id_numbers: [u64];
Initial State
Unlike singleton storage variables, storage vectors do not require initialization. A freshly deployed vector is empty: vec.len() returns 0u32, every vec.get(i) returns none, and the first vec.push(x) makes the vector observable starting at index 0. No constructor setup is needed for vector storage.
Querying
To query the element currently stored in id_numbers at index idx:
id_numbers.get(idx);
This returns u64?. If idx is out of bounds, the result is none.
To get the current length of id_numbers:
id_numbers.len();
This always returns a u32 and cannot fail.
Modifying
To set an element at index idx in id_numbers:
id_numbers.set(idx, value);
To push an element onto the end of id_numbers:
id_numbers.push(value);
To pop and return the last element of id_numbers:
id_numbers.pop();
To remove the element at index idx, return it, and replace it with the final element of id_numbers:
id_numbers.swap_remove(idx);
To clear every element in id_numbers:
id_numbers.clear();
clear()does not actually remove any values from the vector. It simply sets the length to0.- Similarly,
swap_remove()andpop()do not physically remove values. They reduce the length by1, ensuring the final element is no longer accessible.
Usage
fn add_id(new_id: u64) -> Final {
return final {
id_numbers.push(new_id);
};
}
fn remove_id(idx: u32) -> Final {
return final {
id_numbers.swap_remove(idx);
};
}
Storage vector operations are only allowed inside a final { } block or inside a final fn.
External Access
Storage vectors defined in another program can be accessed using the fully qualified form program_name.aleo::storage_name. External storage vectors are read-only and cannot be modified.
For example, suppose another program defines the following storage vector:
program external_program.aleo {
storage id_numbers: [u64];
fn touch() -> Final {
return final {};
}
@noupgrade
constructor() {}
}
You may query elements or the length of this vector from your program:
let first: u64? = external_program.aleo::id_numbers.get(0u32);
let length: u32 = external_program.aleo::id_numbers.len();
External storage vectors cannot be modified. The following operations are invalid:
external_program.aleo::id_numbers.push(5u64); // invalid
external_program.aleo::id_numbers.set(0u32, 5u64); // invalid
external_program.aleo::id_numbers.pop(); // invalid
external_program.aleo::id_numbers.swap_remove(0u32); // invalid
external_program.aleo::id_numbers.clear(); // invalid
Supported value types
Singleton storage variables and storage vectors can hold:
- the primitive types
address,bool,field,group,scalar,signature, and the integer typesi8–i128andu8–u128; - fixed-length arrays whose element type is itself supported;
- structs whose fields are all supported types (checked recursively).
They may not hold records, dyn record, futures, optionals (T?), tuples, mappings, the unit type (), or any zero-sized type.
A storage vector Vector<T> lowers to a u32 => T mapping, so its element type T is one of the value types above — a primitive, array, or struct — and cannot itself be another vector or a mapping. Mapping keys and values accept the same primitive, array, and struct types.