Welcome to the new Golem Cloud Docs! 👋
Documentation
Rust Language Guide
Defining Components

Defining Golem Components in Rust

Creating a project

The simplest way to create a new Golem project is to use cargo component new to create a library project:

$ cargo component new helloworld --lib

This will create a new Rust project in a helloworld directory that implements a Golem component exporting a single, parameterless function which returns the string "Hello, World!".

Using Golem's command line interface instead provides a set of predefined, Golem-specific examples to choose from as a starting point.

To see the available examples for Rust, run:

$ golem-cli list-examples --language rust

The set of examples provided by Golem CLI is defined in the open-source repository golem-examples (opens in a new tab).

Then to create a new project based on the default Rust example, run:

$ golem-cli new --language rust --package-name 'golem:demo' example

The command will create a new Golem project in the example directory, and print short, language-specific instructions on how to build the project.

Specification-first approach

Golem and the Rust toolchain currently requires defining the component's interface using the WebAssembly Interface Type (WIT) format. See the official documentation of this format (opens in a new tab) for reference.

Each new project generated with golem-cli (or cargo component new --lib) contains a wit directory with at least one .wit file defining a world. This world can contain exports (exported functions and interfaces) and these exports will be the compiled Golem component's public API.

The first time a component is compiled (see the Building Components page for details), a bindings.rs file gets generated in the src directory. This module contains the Rust definitions of all the data types and interfaces defined in the WIT file(s).

To implement the specification written in WIT, the Rust code must implement some of these generated traits and export them using a macro defined in the generated bindings module.

Exporting top-level functions

WIT allows exporting one or more top-level functions in the world section, for example:

package golem:demo;
 
world example {
    export hello-world: func() -> string;
}

To implement this function in Rust, the following steps must be taken:

  • make sure the generated bindings module is imported
  • define an empty struct representing our component
  • implement the generated Guest trait for this struct
  • call the export! macro

Let's see in code:

// make sure the generated `bindings` module is imported
#[allow(warnings)]
mod bindings;
 
// define an empty struct representing our component
struct Component;
 
// implement the generated `Guest` trait for this struct
impl bindings::Guest for Component {
    fn hello_world() -> String {
        "Hello, World!".to_string()
    }
}
 
// call the `export!` macro
bindings::export!(Component with_types_in bindings);
⚠️

Note that in WIT, identifiers are using the kebab-case naming convention, while Rust uses the snake_case convention. The generated bindings map between the two automatically.

Exporting interfaces

WIT supports defining and exporting whole interfaces, coupling together multiple functions and possibly custom data types.

Take the following example:

package golem:demo;
 
interface api {
  add: func(value: u64);
  get: func() -> u64;
}
 
world example {
  export api;
}

This is equivalent to having the two exported functions directly exported from the world section, so the implementation is Rust is once again requires to implement the Guest trait from the generated bindings module:

#[allow(warnings)]
mod bindings;
 
struct Component;
 
impl bindings::exports::golem::demo::api::Guest for Component {
    fn add(value: u64) {
        todo!();
    }
 
    fn get() -> u64 {
        todo!();
    }
}
 
bindings::export!(Component with_types_in bindings);

See the Managing state section below to learn the recommended way of managing state in Golem components, which is required to implement these two functions.

Exporting resources

The WIT format supports defining and exporting resources - entities defined by their constructor function and the available methods on them.

Golem supports exporting these resources as part of the worker's API.

The following example modifies the previously seen counter example to define it as a resource, getting the counter's name as a constructor parameter:

package golem:demo;
 
interface api {
  resource counter {
    constructor(name: string);
    add: func(value: u64);
    get: func() -> u64;
  }
}
 
world example {
  export api;
}

Resources can have multiple instances within a worker. Their constructor returns a handle which is then used to call the methods on the resource. Learn more about how resources can be implicitly created and invoked through Golem's APIs in the Invocations page.

To implement the above defined WIT resource in Rust a few new steps must be taken:

  • define a struct representing the resource - it can contain data!
  • implement the trait generated as the resource's interface for this struct
  • specify this type in the Guest trait's implementation

Let's see in code:

#[allow(warnings)]
mod bindings;
 
use std::cell::RefCell;
 
// define a struct representing the resource
struct Counter {
    name: String,
    value: RefCell<u64>,
}
 
// implement the trait generated as the resource's interface for this struct
impl bindings::exports::golem::demo::api::GuestCounter for Counter {
    fn new(name: String) -> Self {
        Self {
            name,
            value: RefCell::new(0),
        }
    }
 
    fn add(&self, value: u64) {
        *self.value.borrow_mut() += value;
    }
 
    fn get(&self) -> u64 {
        *self.value.borrow()
    }
}
 
struct Component;
 
impl bindings::exports::golem::demo::api::Guest for Component {
    type Counter = crate::Counter;
}
 
bindings::export!(Component with_types_in bindings);

Note that the generated trait for the resource is passing non-mutable self references (&self) to the methods, so the resource's internal state must be wrapped in a RefCell to allow mutation.

Data types defined in WIT

The WIT specifications contains some primitive and higher level data types and also allows defining custom data types which can be used as function parameters and return values on the exported functions, interfaces and resources.

The following table shows an example of each WIT data type and its corresponding Rust type:

WIT typeRust type
boolbool
s8, s16, s32, s64i8, i16, i32, i64
u8, u16, u32, u64u8, u16, u32, u64
f32, f64f32, f64
charchar
stringString
list<string>Vec<String>
option<u64>Option<u64>
result<s32, string>Result<i32, String>
result<_, string>Result<(), String>
resultResult<(), ()>
tuple<u64, string, char>(u64, String, char)
record user { id: u64, name: string }struct User { id: u64, name: String }
variant color { red, green, blue, rgb(u32) }enum Color { Red, Green, Blue, Rgb(u32) }
enum color { red, green, blue }enum Color { Red, Green, Blue }
flags access { read, write, lst }bitflags! { pub struct Access: u8 { const READ = 1 << 0; const WRITE = 1 << 1; const LST = 1 << 2; }}

Worker configuration

It is often required to pass configuration values to workers when they are started.

In general Golem supports three different ways of doing this:

  1. Defining a list of string arguments passed to the worker, available as command line arguments
  2. Defining a list of key-value pairs passed to the worker, available as environment variables.
  3. Using resource constructors to pass configuration values to the worker.

Command line arguments

The command line arguments associated with the Golem worker can be accessed in Rust using the standard env::args() function:

for arg in std::env::args() {
    println!("{}", arg);
}

Command line arguments can only be specified when a worker is explicitly created and they are are empty by default, including in cases when the worker was implicitly created by an invocation.

Environment variables

Environment variables can be accessed in Rust using the standard env::var() function:

let value = std::env::var("KEY").expect("KEY was not specified");

Environment variables can be specified when a worker is explicitly created, but there are some environment variables that are always set by Golem:

  • GOLEM_WORKER_NAME - the name of the worker
  • GOLEM_COMPONENT_ID - the ID of the worker's component
  • GOLEM_COMPONENT_VERSION - the version of the component used for this worker

In addition to these, when using Worker to Worker communication, workers created by remote calls inherit the environment variables of the caller.

This feature makes environment variables a good fit for passing configuration such as hostnames, ports, or access tokens to trees of workers.

Resource constructors

As explained earlier, Golem workers can export resources and these resources can have constructor parameters.

Although resources can be used in many ways, one pattern for Golem is only create a single instance of the exported resource in each worker, and use it to pass configuration values to the worker. This is supported by Golem's worker invocation syntax directly, allowing to implicitly create workers and the corresponding resource by a single invocation as described on the Invocations page.

Managing state

Golem workers are stateful. There are two major techniques to store and manipulate state in a Golem worker implemented in Rust:

  1. Using a global thread_local! variable with RefCell
  2. Using resources and RefCell

Note that wrapping the state in RefCell is necessary in both cases to allow mutation.

Using a global thread_local! variable with RefCell

When exporting top-level functions or functions defined in WIT interfaces, the worker state is global. In Rust it is not possible to have mutable global state in safe code so the recommended workaround is to use the thread_local! macro.

Note that Golem workers are always single threaded - the thread_local! macro is used here is just a convenient way to define global state without requiring use of any additional crates or unsafe code.

The following example implements the previously defined counter worker using a thread_local! variable:

#[allow(warnings)]
mod bindings;
 
struct State {
    total: u64,
}
 
thread_local! {
    static STATE: RefCell<State> = RefCell::new(State {
        total: 0,
    });
}
 
struct Component;
 
impl bindings::exports::golem::demo::api::Guest for Component {
    fn add(value: u64) {
        STATE.with_borrow_mut(|state| state.total += value);
    }
 
    fn get() -> u64 {
        STATE.with_borrow(|state| state.total)
    }
}
 
bindings::export!(Component with_types_in bindings);

Using resources and RefCell

When exporting a WIT resource, it is possible to have a per-instance RefCell holding the resource's state, as it was shown above in the Exporting resources section.

Logging

Anything written to the standard output or standard error streams by a Golem worker is captured and can be observed using the worker connect API or the golem-cli worker connect command.

The log crate https://crates.io/crates/log (opens in a new tab) can be used for advanced logging by using the wasi-logger implementation https://crates.io/crates/wasi-logger (opens in a new tab).

This crate requires a one-time initialization step to set up the logger. The easiest way to do this is to expose a dedicated init function from the worker that can be called externally to initialize the worker. If this is not acceptable, the initialization can be done in a OnceCell protected static field, and each exported function must access this field to ensure the logger is initialized.

The following example demonstrates how to use the wasi-logger crate to log messages:

Add the wasi-logger and log crates to the Cargo.toml file:

log = { version = "0.4.22", features = ["kv"] } # the `kv` feature is optional
wasi-logger = { version = "0.1.2", features = ["kv"] }

Then expose an initialization function in the worker:

impl Guest for Component {
    fn init() {
        wasi_logger::Logger::install().expect("failed to install wasi_logger::Logger");
        log::set_max_level(log::LevelFilter::Trace);
    }
    // ...
}

After calling init, all calls to log::info!, etc. will be properly captured by Golem and available through the worker connect API.