<p>Custom types with serializers and deserializers may be created. This is done using two sets of three multimethods which can be extended by the user. For any given type, only one set need be implemented.</p>
<p>Two examples of custom types are given here, one is a 3d vector, and the other an example of a tagged union.</p>
<p>For the vector type, it will serialize to a pointer to an array of three floats.</p>
<p>The multimethod <code>primitive-type</code> returns the primitive type that a given type serializes to. For this example, it should be a pointer.</p>
<p>The <code>reinterpret</code> function allows you to take a segment and decorate it with a new size, and possibly associate it with an arena or add cleanup functions on it.</p>
<p>In cases like this where we don’t know the arena of the pointer, we could use <code>reinterpret</code> to ensure it’s freed. For example if a <code>free-vector!</code> function that takes a pointer exists, we could use this:</p>
<p>For the tagged union type, we will represent the value as a vector of a keyword naming the tag and the value. The type itself will need to take arguments, similar to <code>struct</code>. For example, if we were to represent a result type like in Rust, we might have the following values:</p>
<pre><codeclass="language-clojure">[:ok 5]
[:err "Invalid number format"]
</code></pre>
<p>To represent this, we can have a <code>tagged-union</code> type. For this instance of the result type, it may look like this:</p>
<p>The native representation of these objects is a struct of the tag and a union of the value. In order to correctly serialize the data and pass it to native code, we need a representation of the native layout of the data. The <code>c-layout</code> multimethod provides that.</p>
<p>Types with type arguments are represented as vectors of the type name and any additional arguments. The type name is what is dispatched on for the multimethods.</p>
<p>Now that we have a native layout, we need to be able to serialize and deserialize the value into and out of memory segments. This is accomplished with <code>serialize-into</code> and <code>deserialize-from</code>.</p>
"Gets the index of the first occurance of `item` in `coll`."
[coll item]
(first
(->> coll
(map-indexed vector)
(filter (comp #{item} second))
(map first))))
(defmethod mem/serialize-into ::tagged-union
[obj [_tagged-union tags type-map] segment arena]
(mem/serialize-into
{:tag (item-index tags (first obj))
:value (second obj)}
[::mem/struct
[[:tag ::mem/long]
[:value (get type-map (first obj))]]]
segment
arena))
</code></pre>
<p>This serialization method is rather simple, it just turns the vector value into a map, and serializes it as a struct, choosing the type of the value based on the tag.</p>
<p>Deserialization is a little more complex. First the tag is retrieved from the beginning of the segment, and then the type of the value is decided based on that before it is deserialized.</p>