Expand description

Voluntary Servitude Foreign Function Interface (FFI)

Allows using this rust library as a C library

While vs_t (VoluntaryServitude in C) is thread-safe it’s your responsibility to make sure it exists while pointers to it exist

vs_iter_t (Iter in C) can outlive vs_t and isn’t affected by vs_clear, it is not thread-safe.

It can only be called by one thread (but multiple vs_iter_t of the same vs_t can exist at the same time)

Examples

Single-thread C implementation

#include<assert.h>
#include<stdio.h>
#include "../include/voluntary_servitude.h"

int main(int argc, char **argv) {
    // You are responsible for making sure 'vs' exists while accessed
    vs_t * vs = vs_new();

    // Current vs_t length
    // Be careful with race-conditions since the value, when used, may not be true anymore
    assert(vs_len(vs) == 0);

    const unsigned int data[2] = {12, 25};
    // Inserts void pointer to data to end of vs_t
    vs_append(vs, (void *) &data[0]);
    vs_append(vs, (void *) &data[1]);

    // Creates a one-time lock-free iterator based on vs_t
    vs_iter_t * iter = vs_iter(vs);

    // Clearing vs_t, doesn't change existing iterators
    vs_clear(vs);
    assert(vs_len(vs) == 0);
    assert(vs_iter_len(iter) == 2);

    assert(*(unsigned int *) vs_iter_next(iter) == 12);
    // Index changes as you iter through vs_iter_t
    assert(vs_iter_index(iter) == 1);
    assert(*(unsigned int *) vs_iter_next(iter) == 25);
    assert(vs_iter_index(iter) == 2);

    assert(vs_iter_next(iter) == NULL);
    assert(vs_iter_index(iter) == 2);
    // Index doesn't increase after it gets equal to 'len'
    // Length also is unable to increase after iterator is consumed
    assert(vs_iter_index(iter) == vs_iter_len(iter));

    // Never forget to free vs_iter_t
    assert(vs_iter_destroy(iter) == 0);

    // Create updated vs_iter_t
    vs_iter_t * iter2 = vs_iter(vs);

    // Never forget to free vs_t
    assert(vs_destroy(vs) == 0);

    // vs_iter_t keeps existing after the original vs_t is freed (or cleared)
    assert(vs_iter_len(iter2) == 0);
    assert(vs_iter_next(iter2) == NULL);
    assert(vs_iter_index(iter2) == 0);

    assert(vs_iter_destroy(iter2) == 0);

    printf("Single thread example ended without errors\n");
    (void) argc;
    (void) argv;
    return 0;
}

Multi-producer, multi-consumer C implementation

#include<pthread.h>
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
#include "../include/voluntary_servitude.h"

const unsigned int num_consumers = 8;
const unsigned int num_producers = 4;
const unsigned int num_threads = 12;

const unsigned int num_producer_values = 10000000;
const unsigned int data = 3;

void * producer(void *);
void * consumer(void *);

int main(int argc, char** argv) {
    // You are responsible for making sure 'vs' exists while accessed
    vs_t * vs = vs_new();
    uint8_t thread = 0;
    pthread_attr_t attr;
    pthread_t threads[num_threads];

    if (pthread_attr_init(&attr) != 0) {
        fprintf(stderr, "Failed to initialize pthread arguments.\n");
        exit(-1);
    }

    // Creates producer threads
    for (thread = 0; thread < num_producers; ++thread) {
        if (pthread_create(&threads[thread], &attr, &producer, (void *) vs) != 0) {
            fprintf(stderr, "Failed to create producer thread %d.\n", thread);
            exit(-2);
        }

    }

    // Creates consumers threads
    for (thread = 0; thread < num_consumers; ++thread) {
        if (pthread_create(&threads[num_producers + thread], &attr, &consumer, (void *) vs) != 0) {
            fprintf(stderr, "Failed to create consumer thread %d.\n", thread);
            exit(-3);
        }
    }

    // Join all threads, ensuring vs_t* is not used anymore
    for (thread = 0; thread < num_threads; ++thread) {
        pthread_join(threads[thread], NULL);
    }

    // Never forget to free the memory allocated through the lib
    assert(vs_destroy(vs) == 0);

    printf("Multi-thread C example ended without errors\n");
    (void) argc;
    (void) argv;
    return 0;
}

void * producer(void * vs){
    unsigned int index;
    for (index = 0; index < num_producer_values; ++index) {
        assert(vs_append(vs, (void *) &data) == 0);
    }
    return NULL;
}

void * consumer(void * vs) {
    const unsigned int total_values = num_producers * num_producer_values;
    unsigned int values = 0;

    while (values < total_values) {
        vs_iter_t * iter = vs_iter(vs);
        void * value;

        values = 0;
        while ((value = vs_iter_next(iter)) != NULL) {
            ++values;
        }
        printf("%d elements\n", values);

        // Never forget to free the memory allocated through the lib
        assert(vs_iter_destroy(iter) == 0);
    }
    return NULL;
}

Functions

Initializes logger according to RUST_LOG env var (exists behind the logs feature)
Append element to VoluntaryServitude
Removes all elements from VoluntaryServitude (preserves existing Iter)
Free VoluntaryServitude (preserves existing Iter)
Makes lock-free iterator (Iter) based on VoluntaryServitude
Free Iter (can happen after VoluntaryServitude’s free)
Returns current Iter index
Returns total size of Iter, it may grow, but never decrease
Obtains next element in Iter, returns NULL if there are no more elements
Atomically extracts current size of VoluntaryServitude, be careful with race conditions when using it
Creates new empty VoluntaryServitude

Type Definitions

Iter’s representation in C
VoluntaryServitude’s representation in C