Zomes
A zome (short for chromosome) is a module of executable code within a DNA. It’s the smallest unit of modularity in a Holochain application.
How a zome is structured
A zome is just a WebAssembly module that exposes public functions. The conductor (the Holochain runtime) calls these functions at different points in the application’s lifetime. Some functions have special names and serve as callbacks that are called by the Holochain system. Others are ones you define yourself, and they become your zome’s API that external processes such as a UI can call.
How a zome is written
We’re focusing on Rust as a language for writing zomes, mainly because Holochain is written in Rust, so types can be shared between the host and zomes.
A Rust-based zome is a library crate that’s compiled to the WebAssembly build target. We’ve created an SDK called the Holochain Development Kit (HDK), which lets you define functions, exchange data with the host (the Holochain conductor), and access all of the host’s functionality.
The two types of zomes
Integrity
An integrity zome defines a portion of your application’s data model. This includes not just the structure of the data but also validation rules for operations that manipulate this data.
Keep your integrity zomes small
When you’re writing an integrity zome, use the smaller hdi
crate instead of hdk
, because it’s a subset of the HDK’s functionality that contains everything an integrity zome needs. There’s a lot of functionality in hdk
that can’t be used in an integrity zome’s callbacks, and we recommend against putting anything in your integrity zome other than your data model. hdi
is also more stable than hdk
. Both of these things matter because every change to an integrity zome, including dependency updates, changes the DNA hash, creating a new empty network and database.
Your integrity zome tells Holochain about the types of entries and links it defines with macros called hdk_entry_types
and hdk_link_types
added to enums of all the entry and link types. These create callbacks that are run at DNA install time. Read more in Define an entry type and Define a link type.
Finally, your integrity zome defines validation callbacks that check for correctness of data and actions. Holochain runs this on an agent’s own device when they try to author data, and when they’re asked to store and serve data authored by others.
Create an integrity zome
The easy way to create an integrity zome is to scaffold a new hApp. The scaffolding tool will generate all the project files, including scripts to test and build distributable packages, and it can also scaffold boilerplate code for all your app’s required callbacks and data types.
If you want to create a zome without the scaffolding tool, first make sure you have Rust, Cargo, and the wasm32-unknown-unknown
Rust build target installed on your computer. Then create a library crate:
cargo new my_integrity_zome --lib
Then add some necessary bits to your new Cargo.toml
file:
[package]
name = "my_integrity_zome"
version = "0.1.0"
edition = "2021"
+[lib]
+crate-type = ["cdylib"]
[dependencies]
+hdi = "=0.5.0-rc.1"
+serde = "1.0"
Now you can write a validate
callback and define some entry types.
When you’ve written some code, compile your zome using cargo
:
cargo build --release --target wasm32-unknown-unknown
Your zome will be in target/wasm32-unknown-unknown/release/my_integrity_zome.wasm
.
Coordinator
Coordinator zomes hold your back-end logic — the functions that read and write data or communicate with peers. In addition to some optional lifecycle callbacks , you can also write your own zome functions that serve as your zome’s API.
Create a coordinator zome
Again, the easiest way to create a coordinator zome is to let the scaffolding tool do it for you. But if you want to do it yourself, it’s the same as an integrity zome, with one exception. Use the above additions to the integrity zome’s Cargo.toml
as a template, but change the dependencies like this:
[dependencies]
-hdi = "=0.5.0-rc.1"
+hdk = "=0.4.0-rc.1"
serde = "1.0"
+# If you want to work with the types you defined in your integrity zome,
+# specify a dependency on it here.
+my_integrity_zome = { path = "../my_integrity_zome" }
Define a function
You expose a callback or zome function to the host by making it a pub fn
and adding a macro called hdk_extern
. This handles the task of passing data back and forth between the host and the zome, which is complicated and involves pointers to shared memory.
A zome function must have a single input parameter of any type and return an ExternResult<T>
, where T
is also any type. The input parameter’s type must be deserializable by serde, and the wrapped return value’s type must be serializable. All of the example functions in this guide follow those constraints.
Callbacks are the same, except that they must also use the proper input and output types for the callback’s signature.
Here’s a very simple zome function that takes a name and returns a greeting:
use hdk::prelude::*;
#[hdk_extern]
pub fn say_hello(name: String) -> ExternResult<String> {
Ok(format!("Hello {}!", name))
}
Handling errors
You can handle most errors in a function with the ?
short-circuit operator; the HDK does a good job of converting most of its own error types into ExternResult<T>
and providing the zome name and the line number where the failure happened.
use hdk::prelude::*;
#[hdk_extern]
pub fn get_any_record(hash: AnyDhtHash) -> ExternResult<Option<Record>> {
// Short-circuit any error that `get` might return.
let maybe_record = get(hash, GetOptions::network())?;
Ok(maybe_record)
}
You can also explicitly return an error with the wasm_error
macro:
use hdk::prelude::*;
#[hdk_extern]
pub fn check_age_for_18a_movie(age: u32) -> ExternResult<()> {
if age >= 18 {
return Ok(());
}
Err(wasm_error!("You are too young to watch this movie."))
}
Reference
hdk
cratehdi
cratehdi::hdk_entry_types
hdi::hdk_link_types
hdk_derive::hdk_extern
hdi::map_extern::ExternResult<T>
wasm_error