On this page:

5.4 Serializable C Struct Types🔗

 (require ffi/serialize-cstruct)
  package: serialize-cstruct-lib


(define-serializable-cstruct _id ([field-id type-expr] ...)
                             property ...)
property = #:alignment alignment-expr
  | #:malloc-mode malloc-mode-expr
  | #:serialize-inplace
  | #:deserialize-inplace
  | #:version vers
#:other-versions ([other-vers deserialize-chain-expr
  | #:property prop-expr val-expr
Like define-cstruct, but defines a serializable type, with several changed additional bindings:

Instances of the new type fulfill the serializable? predicate and can be used with serialize and deserialize. Serialization may fail if one of the fields contains an arbitrary pointer, an embedded non-serializable C struct, or a pointer to a non-serializable C struct. Array-types are supported as long as they don’t contain one of these types.

The default vers is 0, and vers must be a literal, exact, non-negative integer. An #:other-versions clause provides deserializers for previous versions of the structure with the name id, so that previously serialized data can be deserialized after a change to the declaration of id. For each other-vers, deserialize-chain-expr should be the value of a deserialize:cstruct:other-id binding for some other "other-id" declared with define-serializable-cstruct that has the same shape that the previous version of id; the function produced by convert-proc-expr should convert an instance of other-id to an instance of id. The functions produced by unconvert-proc-expr and cycle-convert-proc-expr are used if a record is involved in a cycle; the function from unconvert-proc-expr takes an id instance produced by convert-proc-expr’s function back to a other-id, while cycle-convert-proc-expr returns two values: a shell instance of id and function to accept a filled other-id whose content should be moved to the shell instance of id.

The malloc-mode-expr arguments control the memory allocation for this type during deserialization and make-id/mode. It can be one of the mode arguments to malloc, or a procedure
that allocates memory of the given size. The default is malloc with 'atomic.

When #:serialize-inplace is specified, the serialized representation shares memory with the C struct object. While being more efficient, especially for large objects, changes to the object after serialization may lead to changes in the serialized representation.

A #:deserialize-inplace option reuses the memory of the serialized representation, if possible. This option is more efficient for large objects, but it may fall back to allocation via malloc-mode-expr for cyclic structures. As the allocation mode of the serialized representation will be 'atomic by default or may be arbitrary if #:serialize-inplace is specified, inplace deserialisation should be used with caution whenever the object contains pointers.

When the C struct contains pointers, it is advisable to use a custom allocator. It should be based on a non-moving-memory allocation like 'raw, potentially with manual freeing to avoid memory leaks after garbage collection.

Changed in version 1.1 of package serialize-cstruct-lib: Added #:version and #:other-versions.

> (define-serializable-cstruct fish ([color _int]))
> (define f0/s (serialize (make-fish 1)))
> (fish-color (deserialize f0/s))


> (define-serializable-cstruct aq ([a (_gcable fish-pointer)]
                                   [d (_gcable aq-pointer/null)])
    #:malloc-mode 'nonatomic)
> (define aq1 (make-aq/mode (make-fish 6) #f))
> (set-aq-d! aq1 aq1) ; create a cycle
> (define aq0/s (serialize aq1))
> (aq-a (aq-d (aq-d (deserialize aq0/s))))


; Same shape as original aq:
> (define-serializable-cstruct old-aq ([a (_gcable fish-pointer)]
                                       [d (_gcable _pointer)])
          #:malloc-mode 'nonatomic)
; Replace the original aq:
> (define-serializable-cstruct aq ([a (_gcable fish-pointer)]
                                   [b (_gcable fish-pointer)]
                                   [d (_gcable aq-pointer/null)])
    #:malloc-mode 'nonatomic
    #:version 1
    #:other-versions ([0 deserialize-chain:cstruct:old-aq
                         (lambda (oa)
                           (make-aq/mode (old-aq-a oa)
                                         (old-aq-a oa)
                                         (cast (old-aq-d oa) _pointer aq-pointer)))
                         (lambda (a)
                           (make-old-aq/mode (aq-a a)
                                             (aq-d a)))
                         (lambda ()
                           (define tmp-fish (make-fish 0))
                           (define a (make-aq/mode tmp-fish tmp-fish #f))
                           (values a
                                   (lambda (oa)
                                     (set-aq-a! a (old-aq-a oa))
                                     (set-aq-b! a (old-aq-a oa))
                                     (set-aq-d! a (cast (old-aq-d oa) _pointer aq-pointer)))))]))
; Deserialize old instance to new cstruct:
> (fish-color (aq-a (aq-d (aq-d (deserialize aq0/s)))))


> (define aq1/s (serialize (make-aq/mode (make-fish 1) (make-fish 2) #f)))
; New version of fish:
> (define-serializable-cstruct old-fish ([color _int]))
> (define-serializable-cstruct fish ([weight _float]
                                     [color _int])
    #:version 1
    #:other-versions ([0 deserialize-chain:cstruct:old-fish
                         (lambda (of)
                           (make-fish 10.0 (old-fish-color of)))
                         (lambda (a) (error "cycles not possible!"))
                         (lambda () (error "cycles not possible!"))]))
; Deserialized content upgraded to new fish:
> (fish-color (aq-b (deserialize aq1/s)))


> (fish-weight (aq-b (deserialize aq1/s)))