Welcome to the new Golem Cloud Docs! 👋
Documentation
C/C++ Language Guide
Defining Components

Defining Golem Components in C

Creating a project

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

To see the available exampels for Rust, run:

$ golem-cli list-examples --language c

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 one of these examples, run:

$ golem-cli new --example c-actor-minimal --component-name example --package-name 'golem:demo'

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 C/C++ 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 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 couple of files (example.c, example.h and example_component_type.o if the component name was example) get generated in the src directory. This module contains the C definitions of all the data types and interfaces defined in the WIT file(s).

To implement the specification written in WIT, the C code must implement some of these generated exported functions defined in the generated header file.

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 C, the following steps must be taken:

  • include the generated header example.h
  • implement the exported function in C

Let's see in code:

// include the generated header
#include "example.h"
 
// implement the exported function
void example_hello_world(example_string_t *ret) {
    example_string_set(ret, "Hello, World!");
}
⚠️

Note that in WIT, identifiers are using the kebab-case naming convention, while C 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 C is once again requires to implement the two exported functions defined in the generated header:

 
#include "example.h"
 
// Component state
static uint64_t total = 0;
 
// Implementation of the exported functions.
void exports_golem_demo_api_add(uint64_t value) {
  total += value;
}
 
uint64_t exports_golem_demo_api_get() {
  return total;
}

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 C a few new steps must be taken:

  • define a struct representing the resource - it can contain data!
  • implement constructor, methods and destructor for the resource

Let's see in code:

// we need malloc, free and memcpy for the example
#include <stdlib.h>
#include <string.h>
 
#include "example.h"
 
// Define the counter resource's underlying data type
struct exports_golem_demo_api_counter_t {
    example_string_t name;
    uint64_t value;
};
 
// Implement the constructor that allocates memory for the counter struct and stores the counter's name in it
exports_golem_demo_api_own_counter_t exports_golem_demo_api_constructor_counter(example_string_t *name) {
    exports_golem_demo_api_counter_t* instance = (exports_golem_demo_api_counter_t*)malloc(sizeof(exports_golem_demo_api_counter_t));
    instance->name.len = name->len;
    instance->name.ptr = (uint8_t*)malloc(name->len);
    memcpy(instance->name.ptr, name->ptr, name->len);
    exports_golem_demo_api_own_counter_t own = exports_golem_demo_api_counter_new(instance);
    return own;
}
 
// Implement the methods
void exports_golem_demo_api_method_counter_add(exports_golem_demo_api_borrow_counter_t self, uint64_t value) {
    self->value += value;
}
 
uint64_t exports_golem_demo_api_method_counter_get(exports_golem_demo_api_borrow_counter_t self) {
    return self->value;
}
 
// Implement the destructor
void exports_golem_demo_api_counter_destructor(exports_golem_demo_api_counter_t *rep) {
    example_string_free(&rep->name);
}
 

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 C type:

WIT typeC type
boolbool
s8, s16, s32, s64int8_t, int16_t, int32_t, int64_t
u8, u16, u32, u64uint8_t, uint16_t, uint32_t, uint64_t
f32, f64float, double
charuint32_t
stringGenerated custom string type (example_list_string_t)
list<string>typedef struct { example_string_t *ptr; size_t len; } example_list_string_t;
option<u64>typedef struct { bool is_some; uint64_t val; } example_option_u64_t;
result<s32, string>typedef struct { bool is_err; union { int32_t ok; example_string_t err; } val; } example_result_s32_string_t;
result<_, string>typedef struct { bool is_err; union { example_string_t err; } val; } example_result_s32_string_t;
resulttypedef struct { bool } is_err; } example_result_void_void_t;
tuple<u64, string, char>typedef struct { uint64_t f0; example_string_t f1; uint32_t f2; } example_tuple3_u64_string_char32_t;
record user { id: u64, name: string }typedef struct exports_golem_demo_api_user_t { uint64_t id; example_string_t name; } exports_golem_demo_api_user_t;
variant color { red, green, blue, rgb(u32) }typedef struct exports_golem_demo_api_color_t { uint8_t tag; union { uint32_t rgb; } val; } exports_golem_demo_api_color_t; with #defines for the tags
enum color { red, green, blue }typedef uint8_t exports_golem_demo_api_color_t; with #defines for the cases
flags access { read, write, lst }typedef uint8_t exports_golem_demo_api_access_t with #defines for the bit masks

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 by calling using the wasi:cli/environment@0.2.0 WIT interface's get-arguments function.

This requires putting the wit/deps folder from github.com/golemcloud/golem-wit/tree/main/wit/deps in the project's wit/deps directory, and then importing this interface to the component's world:

world example {
    import wasi:cli/environment@0.2.0;
    // ...
}

Then from the C code, it can be called as:

example_list_string_t args;
wasi_cli_environment_get_arguments(&args);

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 C using the standard getenv function:

printf("KEY=%s\n", getenv("KEY"));

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 C:

  1. Using global variables
  2. Using resources and storing state in the resource's struct