Skip to main content

Getting started

Build the library

From bindings/c/:

mkdir -p build && cd build
cmake ..
make

This produces libopendal_c under ../../target/debug/. Link against it and include bindings/c/include/opendal.h.

Your first program

This program builds a memory-backed operator, writes data, reads it back, inspects the metadata, and frees every heap-allocated value. It requires no credentials and no filesystem access.

#include <assert.h>
#include <stdio.h>
#include "opendal.h"

int main(void)
{
/* Build an operator for the "memory" service with no options. */
opendal_result_operator_new result = opendal_operator_new("memory", NULL);
assert(result.op != NULL);
assert(result.error == NULL);
opendal_operator *op = result.op;

/* Write bytes to a path. */
const char *msg = "Hello, OpenDAL!";
opendal_bytes data = {
.data = (uint8_t *)msg,
.len = 15,
};
opendal_error *err = opendal_operator_write(op, "/hello.txt", &data);
assert(err == NULL);

/* Read the bytes back. */
opendal_result_read r = opendal_operator_read(op, "/hello.txt");
assert(r.error == NULL);
assert(r.data.len == 15);
printf("%.*s\n", (int)r.data.len, r.data.data); /* Hello, OpenDAL! */
opendal_bytes_free(&r.data);

/* Stat the path to get metadata. */
opendal_result_stat s = opendal_operator_stat(op, "/hello.txt");
assert(s.error == NULL);
printf("size = %llu\n", (unsigned long long)opendal_metadata_content_length(s.meta));
opendal_metadata_free(s.meta);

/* Delete the file. */
opendal_error *del_err = opendal_operator_delete(op, "/hello.txt");
assert(del_err == NULL);

opendal_operator_free(op);
return 0;
}

Every operation returns either an opendal_error * (NULL on success) or a result struct whose .error field is NULL on success. See Error handling below.

Point it at a real backend

The only change is the scheme and the options you pass to opendal_operator_new. Operations stay identical across all services.

/* Filesystem operator rooted at /tmp/mydata */
opendal_operator_options *opts = opendal_operator_options_new();
opendal_operator_options_set(opts, "root", "/tmp/mydata");

opendal_result_operator_new result = opendal_operator_new("fs", opts);
opendal_operator_options_free(opts); /* options are copied; free immediately */

assert(result.op != NULL);
opendal_operator *op = result.op;
/* ... same write/read/stat/delete calls ... */
opendal_operator_free(op);

For S3 or any other service, set the configuration keys documented on /services as key-value pairs via opendal_operator_options_set.

Error handling

C has no exceptions. Every function that can fail returns either a bare opendal_error * or a result struct with an .error field:

  • NULL → success.
  • non-NULL → failure; inspect .code (an opendal_code enum) and .message (an opendal_bytes containing the error text, not NUL-terminated). Call opendal_error_free when done.
opendal_result_read r = opendal_operator_read(op, "/no-such-file");
if (r.error != NULL) {
/* .message is NOT NUL-terminated; use the length. */
fprintf(stderr, "error %d: %.*s\n",
r.error->code,
(int)r.error->message.len,
r.error->message.data);
opendal_error_free(r.error);
/* r.data is invalid when error != NULL */
}

Common error codes (from opendal_code):

CodeMeaning
OPENDAL_NOT_FOUNDPath does not exist
OPENDAL_PERMISSION_DENIEDInsufficient permissions
OPENDAL_ALREADY_EXISTSPath already exists
OPENDAL_UNSUPPORTEDOperation not supported by this service
OPENDAL_CONFIG_INVALIDOperator configuration is invalid

Memory ownership rules

Every heap-allocated value returned by OpenDAL must be freed by the caller:

TypeFree with
opendal_operator *opendal_operator_free
opendal_bytes (field, not pointer)opendal_bytes_free (pass by address)
opendal_error *opendal_error_free
opendal_metadata *opendal_metadata_free
opendal_lister *opendal_lister_free
opendal_entry *opendal_entry_free
opendal_reader *opendal_reader_free
opendal_writer *opendal_writer_free
char * returned by metadata / entry / info accessorsopendal_string_free
opendal_operator_options *opendal_operator_options_free

Do not use free() directly on any of these; they are allocated inside the Rust runtime and must be released through the provided functions.