automerge/rust/automerge-c/README.md
Conrad Irwin c3932e6267
Improve docs for building automerge-c on a mac (#465)
* More detailed instructions in README

I struggled to get the project to build for a while when first getting
started, so have added some instructions; and also some usage
instructions for automerge-c that show more clearly what is happening
without `AMpush()`
2022-12-09 13:46:23 +00:00

158 lines
5.8 KiB
Markdown

automerge-c exposes an API to C that can either be used directly or as a basis
for other language bindings that have good support for calling into C functions.
# Building
See the main README for instructions on getting your environment set up, then
you can use `./scripts/ci/cmake-build Release static` to build automerge-c.
It will output two files:
- ./build/Cargo/target/include/automerge-c/automerge.h
- ./build/Cargo/target/release/libautomerge.a
To use these in your application you must arrange for your C compiler to find
these files, either by moving them to the right location on your computer, or
by configuring the compiler to reference these directories.
- `export LDFLAGS=-L./build/Cargo/target/release -lautomerge`
- `export CFLAGS=-I./build/Cargo/target/include`
If you'd like to cross compile the library for different platforms you can do so
using [cross](https://github.com/cross-rs/cross). For example:
- `cross build --manifest-path rust/automerge-c/Cargo.toml -r --target aarch64-unknown-linux-gnu`
This will output a shared library in the directory `rust/target/aarch64-unknown-linux-gnu/release/`.
You can replace `aarch64-unknown-linux-gnu` with any [cross supported targets](https://github.com/cross-rs/cross#supported-targets). The targets below are known to work, though other targets are expected to work too:
- `x86_64-apple-darwin`
- `aarch64-apple-darwin`
- `x86_64-unknown-linux-gnu`
- `aarch64-unknown-linux-gnu`
As a caveat, the header file is currently 32/64-bit dependant. You can re-use it
for all 64-bit architectures, but you must generate a specific header for 32-bit
targets.
# Usage
For full reference, read through `automerge.h`, or to get started quickly look
at the
[examples](https://github.com/automerge/automerge-rs/tree/main/rust/automerge-c/examples).
Almost all operations in automerge-c act on an AMdoc struct which you can get
from `AMcreate()` or `AMload()`. Operations on a given doc are not thread safe
so you must use a mutex or similar to avoid calling more than one function with
the same AMdoc pointer concurrently.
As with all functions that either allocate memory, or could fail if given
invalid input, `AMcreate()` returns an `AMresult`. The `AMresult` contains the
returned doc (or error message), and must be freed with `AMfree()` after you are
done to avoid leaking memory.
```
#include <automerge-c/automerge.h>
#include <stdio.h>
int main(int argc, char** argv) {
AMresult *docResult = AMcreate(NULL);
if (AMresultStatus(docResult) != AM_STATUS_OK) {
printf("failed to create doc: %s", AMerrorMessage(docResult).src);
goto cleanup;
}
AMdoc *doc = AMresultValue(docResult).doc;
// useful code goes here!
cleanup:
AMfree(docResult);
}
```
If you are writing code in C directly, you can use the `AMpush()` helper
function to reduce the boilerplate of error handling and freeing for you (see
examples/quickstart.c).
If you are wrapping automerge-c in another language, particularly one that has a
garbage collector, you can call `AMfree` within a finalizer to ensure that memory
is reclaimed when it is no longer needed.
An AMdoc wraps an automerge document which are very similar to JSON documents.
Automerge documents consist of a mutable root, which is always a map from string
keys to values. Values can have the following types:
- A number of type double / int64_t / uint64_t
- An explicit true / false / nul
- An immutable utf-8 string (AMbyteSpan)
- An immutable array of arbitrary bytes (AMbyteSpan)
- A mutable map from string keys to values (AMmap)
- A mutable list of values (AMlist)
- A mutable string (AMtext)
If you read from a location in the document with no value a value with
`.tag == AM_VALUE_VOID` will be returned, but you cannot write such a value explicitly.
Under the hood, automerge references mutable objects by the internal object id,
and `AM_ROOT` is always the object id of the root value.
There is a function to put each type of value into either a map or a list, and a
function to read the current value from a list. As (in general) collaborators
may edit the document at any time, you cannot guarantee that the type of the
value at a given part of the document will stay the same. As a result reading
from the document will return an `AMvalue` union that you can inspect to
determine its type.
Strings in automerge-c are represented using an `AMbyteSpan` which contains a
pointer and a length. Strings must be valid utf-8 and may contain null bytes.
As a convenience you can use `AMstr()` to get the representation of a
null-terminated C string as an `AMbyteSpan`.
Putting all of that together, to read and write from the root of the document
you can do this:
```
#include <automerge-c/automerge.h>
#include <stdio.h>
int main(int argc, char** argv) {
// ...previous example...
AMdoc *doc = AMresultValue(docResult).doc;
AMresult *putResult = AMmapPutStr(doc, AM_ROOT, AMstr("key"), AMstr("value"));
if (AMresultStatus(putResult) != AM_STATUS_OK) {
printf("failed to put: %s", AMerrorMessage(putResult).src);
goto cleanup;
}
AMresult *getResult = AMmapGet(doc, AM_ROOT, AMstr("key"), NULL);
if (AMresultStatus(getResult) != AM_STATUS_OK) {
printf("failed to get: %s", AMerrorMessage(getResult).src);
goto cleanup;
}
AMvalue got = AMresultValue(getResult);
if (got.tag != AM_VALUE_STR) {
printf("expected to read a string!");
goto cleanup;
}
printf("Got %zu-character string `%s`", got.str.count, got.str.src);
cleanup:
AMfree(getResult);
AMfree(putResult);
AMfree(docResult);
}
```
Functions that do not return an `AMresult` (for example `AMmapItemValue()`) do
not allocate memory, but continue to reference memory that was previously
allocated. It's thus important to keep the original `AMresult` alive (in this
case the one returned by `AMmapRange()`) until after you are done with the return
values of these functions.
Beyond that, good luck!