Dynamic Type System: facil.io objects (FIOBJ)

In this page you will find a general overview. For detailed API information please visit the Core or Type pages.


facil.io offers a dynamic type system that makes it a breeze to mix object types together.

This dynamic type system is an independent module within the facil.io core and can be used separately.

The FIOBJ type API is divided by it's inner types (tested using FIOBJ_TYPE(obj) or FIOBJ_TYPE_IS(obj, type)):

Why we need dynamic types?

C doesn't lend itself easily to the dynamic types that are often used in languages such as Javascript. This makes it harder to use an optimized C backend (server) when the frontend (client / browser) expects multi-type responses such as JSON objects.

To resolve this difference in expectations, facil.io offers the FIOBJ type system.

This is an opaque type that can be tested using FIOBJ_TYPE(obj) or FIOBJ_TYPE_IS(obj, type).

This offers the following advantages (among others):

API Considerations

This is a short summery regarding the API and it's use. The fiobj_* API is well documented in the header files, so only main guidelines are mentioned.

Functional Access

All object access should be functional, or using the macros provided. Although this requirement can be circumvented, using the functional interface should be preferred.

For example:

/* this will work */
FIOBJ str = fiobj_str_buf(6); /* automatically adds room for the NUL terminator */
fio_cstr_s raw_str = fiobj_obj2cstr(str);
memcpy(raw_str.buffer, "Hello!", 6);
fiobj_str_resize(str, 6);
// ...

/* this is better */
FIOBJ str = fiobj_str_buf(6);
fiobj_str_write(str, "Hello!", 6);
// ...

/* for simple strings, one line will do */
FIOBJ str = fiobj_str_new("Hello!", 6);
// ...

/* for more complex cases, printf style is supported */
FIOBJ str = fiobj_strprintf("%s %d" , "Hello!", 42)
// ...

/* or */
FIOBJ str = fiobj_str_buf(0);
fiobj_str_write2(str, "%s %d" , "Hello!", 42);
// ...

/* for static strings, this is preferred */
FIOBJ str = fiobj_str_static("Hello!", 6);
// ...

Ownership Follows Nesting

An object's memory should always be managed by it's "owner". This usually means the calling function.

However, when an object is nested within another object (i.e., placed in an Array or set as the value for a Hash or an HTTP header), the ownership of the object is transferred.

In the following example, the String nested within the Array is freed when the Array is freed:

FIOBJ ary = fiobj_ary_new();
FIOBJ str = fiobj_str_new("Hello!", 6);
fiobj_ary_push(ary, str);
// ...

Hashes follow the same rule. However...

It's important to note that Hash keys ownership isn't transferred to the Hash (keys are used to access and store data, but they are not the data itself).

When calling fiobj_hash_set, we are storing a value in the Hash, the key is what we use to access that value. This is why the key's ownership remains with the calling function. i.e.:

FIOBJ h = fiobj_hash_new();
FIOBJ key = fiobj_str_new("life", 4);
/* By placing the Number in the Hash, it will be deallocated together with the Hash */
fiobj_hash_set(h, key, fiobj_num_new(42));
// ...
fiobj_free(h); /* Free the Hash and it's data, but NOT the key */
// ...
/* eventually we need to free the key */

Passing By Reference

All objects are passed along by reference. The dup (duplication) process simply increases the reference count.

This is a very powerful tool. In the following example, str2 is a "copy" by reference of str. By editing str2 we're also editing str:

FIOBJ str = fiobj_str_new("Hello!", 6);
FIOBJ str2 = fiobj_dup(str);
/* We'll edit str2 to say "Hello There!" instead of "Hello!" */
fiobj_str_resize(str2, 5);
fiobj_str_write(str2, " There!", 7);
/* This prints "Hello There!" because str was edited by reference! */
printf("%s\n", fiobj_obj2cstr(str).data);
/* we need to free both references to free the memory */

An independent copy can be created using an object's specific copy function. This example create a new, independent, object instead of referencing the old one:

FIOBJ str = fiobj_str_new("Hello!", 6);
/* create a copy instead of a reference */
FIOBJ str2 = fiobj_str_copy(str);
/* this is the same as */
FIOBJ str3 = fiobj_str_new(fiobj_obj2cstr(str).data, fiobj_obj2cstr(str).len);
// ...

Copy by reference produces a deep reference adjustment, so Arrays and Hashes can be safely copied by reference.

FIOBJ ary = fiobj_ary_new();
fiobj_ary_push(ary, fiobj_str_new("Hello!", 6));
FIOBJ ary_copy = fiobj_dup(ary);
// ...
// all the items in ary2 are still accessible.
fprintf(stderr, "%s\n", fiobj_obj2cstr( fiobj_ary_index(ary_copy, -1) ).buffer );

Cyclic Nesting Errors

Cyclic protection is unsupported mostly because of performance concerns, but also because cyclic nesting is impractical for network applications (for example, how would a cyclic object be formatted into JSON?).

Cyclic nesting should be avoided. For example, the following code will crash:

FIOBJ ary = fiobj_ary_new();
FIOBJ ary2 = fiobj_ary_new();
// cyclic nesting
fiobj_ary_push(ary, ary2);
fiobj_ary_push(ary2, ary);
// free might crash or produce unexpected results
// each2 will cycle forever
fiobj_each2(ary2, ...);


The FIOBJ module is independent and can be extracted from facil.io by copying the fiobj.h file (under lib/facil/core/types) and all the files in the lib/facil/core/types/fiobj folder.

Place these files in your project and use to your heart's content.

The module is licensed under the same MIT license offered by the rest of the facil.io source code.