On this page:
1.5.1 Declaring Union Types
1.5.2 Hiding Pointers through Conversion

1.5 Foreign Unions and Pointer Arithmetic🔗ℹ

At the Rhombus level, when a representation involves a choice between different shapes, then a predicate associated with each shape easily distinguishes the cases. At the C level, different shapes can be merged with a union type to superimpose representations while relying on some explicit indicator of which choice applies at any given point.

This level of representation detail and punning is closely related to pointer arithmetic, which also ends up being used in similar places. As in C, the ffi library provides a convenient veneer for certain forms of pointer arithmetic by viewing pointers as references to arrays.

1.5.1 Declaring Union Types🔗ℹ

Let’s work with Cairo path objects, which encode vector-graphics drawing inputs. A path in Cairo is defined as a cairo_path_t:

  typedef struct {

      cairo_status_t status;

      cairo_path_data_t *data;

      int num_data;

  } cairo_path_t;

The data field is a pointer, but not to just one cairo_path_data_t; it is a pointer to an array of cairo_path_data_ts, where num_data indicates the length of that array.

Individual elements of the array are defined by a union:

  union _cairo_path_data_t {

      struct {

          cairo_path_data_type_t type;

          int length;

      } header;

      struct {

          double x, y;

      } point;

  };

That is, each element is either a header or a point. A header is followed by length-1 points. The cairo_path_data_type_t within a header is an integer that indicates whether it encodes a move-to, line-to, etc., operation.

For the Rhombus version of these types, it will be helpful to pull out path_header_t and path_point_t into their own definitions before combining them with foreign.union.

foreign.enum cairo_path_data_type_t int_t

| move_to

| line_to

| curve_to

| close_path

foreign.struct path_header_t(

  type :: cairo_path_data_type_t,

  length :: int_t

)

foreign.struct path_point_t(

  x :: double_t,

  y :: double_t

)

foreign.union cairo_path_data_t(

  header :: path_header_t,

  point :: path_point_t

)

foreign.type cairo_status_t = int_t

foreign.struct cairo_path_t(

  status :: cairo_status_t,

  data :: cairo_path_data_t*,

  num_data :: int_t

)

cairo.fun cairo_path_destroy(cairo_path_t*) :: void_t:

  ~wrap: finalize.deallocator()

cairo.fun cairo_copy_path(cairo_t*) :: cairo_path_t*:

  ~wrap: finalize.allocator(cairo_path_destroy)

Let’s create a fresh context, add path elements in it, and get the accumulated path before it is used by cairo_stroke:

def (bt, cr) = make()

cairo_move_to(cr, 50.0, 50.0)

cairo_line_to(cr, 206.0, 206.0)

cairo_move_to(cr, 50.0, 206.0)

cairo_line_to(cr, 115.0, 115.0)

def a_path = cairo_copy_path(cr)

cairo_stroke(cr)

cairo_destroy(cr)

The path should have status 0 (success), and it should have 8 components:

> a_path.status

0

> a_path.num_data

8

> def data = a_path.data

> data

#<cairo_path_data_t*>

To access an individual element of the data array, use array indexing on the cairo_path_data_t* pointer. Since foreign.union defines cairo_path_data_t, the pointer type cairo_path_data_t* carries static information that enables [] indexing:

> data[0]

#<cairo_path_data_t*>

A cairo_path_data_t can be either the header case or the point case. The foreign.union definition gives us .header and .point to access the union fields as either a path_header_t or path_point_t, respectively. We know that the first element of data must be the header case.

> def head1 = data[0].header

> head1.type

#'move_to

> head1.length

2

A #'move_to command has a single point, so we know that the second element of data is the point case.

> def pt1 = data[1].point

> pt1.x

50.0

> pt1.y

50.0

Next is #'line_to, and so on:

> def head2 = data[2].header

> head2.type

#'line_to

> def pt2 = data[3].point

> pt2.x

206.0

> pt2.y

206.0

Now that we’re done with this experiment, let’s be good citizens by cleaning up, although finalization will clean up after us if it must.

> cairo_path_destroy(a_path)

Operations like data[i] and field accessors like .header (or, more generally, accessors that access compound types within other compound types) ultimately perform a kind of pointer arithmetic. In case you ever need to take control of pointer arithmetic yourself, ffi provides mem with & for shifting pointer addresses by a given offset.

1.5.2 Hiding Pointers through Conversion🔗ℹ

The Cairo path example illustrates how to traverse a complex structure, but users of a set of Cairo bindings likely will not want to deal with all of that complexity. Let’s define a new type that converts the C representation on demand by wrapping it as a sequence that’s compatible with for.

To make a sequence, we need a new Rhombus class that implements Sequenceable. We’ll define auto_cairo_path_t as a type that wraps a pointer as a CairoPath instance.

class CairoPath(ptr :~ cairo_path_t):

  implements Sequenceable

  override method to_sequence():

    in_cairo_path(this)

foreign.type auto_cairo_path_t:

  ~extends: cairo_path_t*

  ~predicate: fun (v): v is_a CairoPath

  ~c_to_rhombus: fun (p): CairoPath(p)

  ~rhombus_to_c: fun (rkt :~ CairoPath): rkt.ptr

The reference to in_cairo_path is the doorway to the main conversion, which iterates through Cairo path data:

fun in_cairo_path(path :~ CairoPath):

  def pp = path.ptr

  def array_ptr = pp.data

  def len = pp.num_data

  Sequence.make(

    ~initial_position: 0,

    ~continue_at_position: fun (pos): pos < len,

    ~position_to_element:

      fun (pos):

        def header = array_ptr[pos].header

        def type = header.type

        def count = header.length - 1

        [type,

         & for List (i in 0 .. count):

             def pt = array_ptr[pos + 1 + i].point

             [pt.x, pt.y]],

    ~position_to_next:

      fun (pos): pos + array_ptr[pos].header.length

  )

Let’s redefine cairo_copy_path and cairo_path_destroy and try the earlier example again.

cairo.fun cairo_path_destroy (auto_cairo_path_t) :: void_t:

  ~wrap: finalize.deallocator()

cairo.fun cairo_copy_path (cairo_t*) :: auto_cairo_path_t:

  ~wrap: finalize.allocator(cairo_path_destroy)

def (bt, cr) = make()

cairo_move_to(cr, 50.0, 50.0)

cairo_line_to(cr, 206.0, 206.0)

cairo_move_to(cr, 50.0, 206.0)

cairo_line_to(cr, 115.0, 115.0)

def auto_path :~ Sequence = cairo_copy_path(cr)

cairo_stroke(cr)

cairo_destroy(cr)

> for (elem in auto_path):

    println(elem)

[#'move_to, [50.0, 50.0]]

[#'line_to, [206.0, 206.0]]

[#'move_to, [50.0, 206.0]]

[#'line_to, [115.0, 115.0]]

> cairo_path_destroy(auto_path)