Hey, I'd like to write a binding for a C library, what would be the steps for that ?
Hi @Slazaa,
We don't yet support automatic generation of glue code for c platforms. If you want, you can call c functions from a rust platform. We do support automatic generation of rust glue code.
How does glue work ? How hard would it be not to use it ?
Glue allows the platform and roc to talk to each other.
example of glue for basic-cli
How hard would it be not to use it ?
I have not done this manually myself but from what I've heard, it's very tedious and error prone to do it manually.
Another possibility is to do all communication between the Roc code and the C platform using a String, like here. Also tedious, but simpler overall.
@Luke Boswell did manage to make the String based communication work quite well with a zig platform but I can't find the repo.
This is my attempt at implementing a minimal host.c. I have no idea if any of it is correct:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void *roc_alloc(size_t size, unsigned int alignment)
{
return malloc(size);
}
void *roc_realloc(void *ptr, size_t new_size, size_t old_size, unsigned int alignment)
{
return realloc(ptr, new_size);
}
void roc_dealloc(void *ptr, unsigned int alignment)
{
free(ptr);
}
__attribute__((noreturn)) void roc_panic(void *ptr, unsigned int tag_id)
{
}
void *roc_memset(void *str, int c, size_t n)
{
return memset(str, c, n);
}
struct RocStr { char *bytes; size_t len; size_t capacity; }; // BROKEN!! Missing small-str-optim.!
void roc__mainForHost_1_exposed(char *output); // roc_main
size_t roc__mainForHost_1_exposed_size(); // roc_main_size
void roc__mainForHost_0_caller(const char *flags, const char *closure_data, char *output); // call_Fx
size_t roc__mainForHost_0_size(); // size_Fx
size_t roc__mainForHost_0_result_size(); // size_Fx_result
int main()
{
size_t size = roc__mainForHost_1_exposed_size();
char *returnedFromMain_data_ptr = roc_alloc(size, 1);
// calling "main" function in main.roc:
roc__mainForHost_1_exposed(returnedFromMain_data_ptr);
const unsigned char flags = 0;
size = roc__mainForHost_0_result_size();
char *buffer = roc_alloc(size, 1);
// "returnedFromMain_data_ptr" contains a closure, we call it here:
roc__mainForHost_0_caller(&flags, returnedFromMain_data_ptr, buffer);
// Done everything:
roc_dealloc(buffer, 1);
roc_dealloc(returnedFromMain_data_ptr, 1);
}
I assume that c glue code would look simiilar..
At first glance that looks reasonable except roc_panic
should print a message and call exit
.
Its argument is a RocStr*
I think
Brian Carroll said:
At first glance that looks reasonable except
roc_panic
should print a message and callexit
.
I left out the details but my confidence in posting that betrays my confusion:
This example:
const program = roc__mainForHost_1_exposed();
https://github.com/roc-lang/roc/blob/13703eea7da82691a7bc2030f9c069685f6433c8/examples/cli/tui-platform/host.zig#L176
calls roc__mainForHost_1_exposed() and then simply THROWS AWAY (!) the result:
_ = program;
https://github.com/roc-lang/roc/blob/13703eea7da82691a7bc2030f9c069685f6433c8/examples/cli/tui-platform/host.zig#L194
I have no idea why! Is it to init the main in main.roc? Why is the result (program) not being used? Wish there was better (or any!) documentation on how to interface with roc!
Roc is built by volunteers working for free in their spare time. Documentation takes time to write and we are in the process of totally changing how this works.
I'm not sure why that result is thrown away.
@Folkert de Vries might know
Brian Carroll said:
Roc is built by volunteers working for free in their spare time. Documentation takes time to write and we are in the process of totally changing how this works.
Yeah I mean just a comment or two would be helpful here!
Of course I understand this is a volunteer effort. Hope I can learn enough and find the time to contribute in whatever little way I can!!
ah, we're doing something slightly cheaty here. The zig code assumes that the functions returned by roc's main function are top-level functions, in other words they don't capture anything from their surrounding environment
that is a valid assumption for the applications we write, but in general not entirely correct. (we want to actively enforce this restriction in the future)
so, normally, the result of main would be a struct containing 3 functions, which in practice would be a struct containing for each function the captured values. Because there are none, this is in effect a zero-sized struct: there is no information in it
so we can just ignore it
Folkert de Vries said:
ah, we're doing something slightly cheaty here. The zig code assumes that the functions returned by roc's main function are top-level functions, in other words they don't capture anything from their surrounding environment
Haha! Does this not contradict the very idea behind bot Roc & Zig? Such as Freedom from Side-effects, Immutability, and No Hidden Control-flow??
Aside from that, is it possible to split the mainFromHost into three? => initFromHost, updateFromHost, viewFromHost?? Or do they HAVE to be in a record? Or is this an open issue?
It's an open issue https://github.com/roc-lang/roc/issues/6115
This is how I implemented Model View Update just passing a List U8 between Roc and Zig. It worked ok for that experiment
Last updated: Jul 06 2025 at 12:14 UTC