On this page:
make-cstruct-type
_ list-struct
define-cstruct

3.7 C Struct Types

(make-cstruct-type types)  ctype?
  types : (listof ctype?)
The primitive type constructor for creating new C struct types. These types are actually new primitive types; they have no conversion functions associated. The corresponding Racket objects that are used for structs are pointers, but when these types are used, the value that the pointer refers to is used, rather than the pointer itself. This value is basically made of a number of bytes that is known according to the given list of types list.

(_list-struct type ...+)  ctype?
  type : ctype?
A type constructor that builds a struct type using make-cstruct-type function and wraps it in a type that marshals a struct as a list of its components. Note that space for structs must to be allocated; the converter for a _list-struct type immediately allocates and uses a list from the allocated space, so it is inefficient. Use define-cstruct below for a more efficient approach.

(define-cstruct id/sup ([field-id type-expr] ...))
 
id/sup = _id
  | (_id super-id)
Defines a new C struct type, but unlike _list-struct, the resulting type deals with C structs in binary form, rather than marshaling them to Racket values. The syntax is similar to define-struct, providing accessor functions for raw struct values (which are pointer objects). The new type uses pointer tags to guarantee that only proper struct objects are used. The _id must start with _.

The resulting bindings are as follows:

Objects of the new type are actually C pointers, with a type tag that is a list that contains the string form of id. Since structs are implemented as pointers, they can be used for a _pointer input to a foreign function: their address will be used. To make this a little safer, the corresponding cpointer type is defined as _id-pointer. The _id type should not be used when a pointer is expected, since it will cause the struct to be copied rather than use the pointer value, leading to memory corruption.

If the first field is itself a cstruct type, its tag will be used in addition to the new tag. This feature supports common cases of object inheritance, where a sub-struct is made by having a first field that is its super-struct. Instances of the sub-struct can be considered as instances of the super-struct, since they share the same initial layout. Using the tag of an initial cstruct field means that the same behavior is implemented in Racket; for example, accessors and mutators of the super-cstruct can be used with the new sub-cstruct. See the example below.

Providing a super-id is shorthand for using an initial field named super-id and using _super-id as its type. Thus, the new struct will use _super-id’s tag in addition to its own tag, meaning that instances of _id can be used as instances of _super-id. Aside from the syntactic sugar, the constructor function is different when this syntax is used: instead of expecting a first argument that is an instance of _super-id, the constructor will expect arguments for each of _super-id’s fields, in addition for the new fields. This adjustment of the constructor is, again, in analogy to using a supertype with define-struct.

Note that structs are allocated as atomic blocks, which means that the garbage collector ignores their content. Currently, there is no safe way to store pointers to GC-managed objects in structs (even if you keep a reference to avoid collecting the referenced objects, a the 3m variant’s GC will invalidate the pointer’s value). Thus, only non-pointer values and pointers to memory that is outside the GC’s control can be placed into struct fields.

As an example, consider the following C code:

  typedef struct { int x; char y; } A;

  typedef struct { A a; int z; } B;

  

  A* makeA() {

    A *p = malloc(sizeof(A));

    p->x = 1;

    p->y = 2;

    return p;

  }

  

  B* makeB() {

    B *p = malloc(sizeof(B));

    p->a.x = 1;

    p->a.y = 2;

    p->z   = 3;

    return p;

  }

  

  char gety(A* a) {

    return a->y;

  }

Using the simple _list-struct, you might expect this code to work:

  (define makeB
    (get-ffi-obj 'makeB "foo.so"
      (_fun -> (_list-struct (_list-struct _int _byte) _int))))
  (makeB) ; should return '((1 2) 3)

The problem here is that makeB returns a pointer to the struct rather than the struct itself. The following works as expected:

  (define makeB
    (get-ffi-obj 'makeB "foo.so" (_fun -> _pointer)))
  (ptr-ref (makeB) (_list-struct (_list-struct _int _byte) _int))

As described above, _list-structs should be used in cases where efficiency is not an issue. We continue using define-cstruct, first define a type for A which makes it possible to use makeA:

  (define-cstruct _A ([x _int] [y _byte]))
  (define makeA
    (get-ffi-obj 'makeA "foo.so"
      (_fun -> _A-pointer))) ; using _A is a memory-corrupting bug!
  (define a (makeA))
  (list a (A-x a) (A-y a))
  ; produces an A containing 1 and 2

Using gety is also simple:

  (define gety
    (get-ffi-obj 'gety "foo.so"
      (_fun _A-pointer -> _byte)))
  (gety a) ; produces 2

We now define another C struct for B, and expose makeB using it:

  (define-cstruct _B ([a _A] [z _int]))
  (define makeB
    (get-ffi-obj 'makeB "foo.so"
      (_fun -> _B-pointer)))
  (define b (makeB))

We can access all values of b using a naive approach:

  (list (A-x (B-a b)) (A-y (B-a b)) (B-z b))

but this is inefficient as it allocates and copies an instance of A on every access. Inspecting the tags (cpointer-tag b) we can see that A’s tag is included, so we can simply use its accessors and mutators, as well as any function that is defined to take an A pointer:

  (list (A-x b) (A-y b) (B-z b))
  (gety b)

Constructing a B instance in Racket requires allocating a temporary A struct:

  (define b (make-B (make-A 1 2) 3))

To make this more efficient, we switch to the alternative define-cstruct syntax, which creates a constructor that expects arguments for both the super fields and the new ones:

  (define-cstruct (_B _A) ([z _int]))
  (define b (make-B 1 2 3))