ok, I finally resolved this using tuples in the following way:
type
MyData = tuple[x, y, z: int, s: cstring]
proc new_list(data: ptr MyData): ptr MyData {.cdecl, exportc} =
var data = cast[ptr MyData](alloc0(sizeof(MyData)))
data.x = 111
data.y = 222
data.z = 333
data.s = "nim rules!"
return data
proc destroy_list(data: ptr MyData): void {.cdecl, exportc} =
dealloc(data)
On the Ruby side I'm using the FFI to map the tuple to a struct-like object,
making sure I get the byte offsets right (receiving pointer has offset 0)
class MyObject < FFI::Struct
layout :data1, :int, 0,
:data2, :int, 8,
:data3, :int, 16,
:name, :pointer, 24
I can now use Nim-generated data from Ruby, making sure I release the
structure's memory when I'm done
my_data = NimDemo::MyObject.new(NimDemo.new_list)
puts my_data[:data1]
puts my_data[:data2]
puts my_data[:data3]
puts my_data[:name].read_string
# finished with it, so release it
NimDemo.destroy_list(my_data)
That means I can now pass a list of numeric values and strings (or combinations
thereof) from Nim to Ruby. Which is pretty tidy
Many thanks to @mashingan and @Stefan_Salewski for steering me in the right
direction!