~tristan957/toption

8615cbeab8c43ea0ad4b5bf865792813ebcb06f0 — Tristan Partin 8 months ago 7061b52 master
Make final changes and add write up
9 files changed, 156 insertions(+), 51 deletions(-)

M README.md
A benchmark/benchmark.c
R docs/{meson.build => mark/benchmark.h}
A benchmark/meson.build
M include/toption/toption.h
M lib/toption.c
M meson.build
M meson_options.txt
M tests/toption.c
M README.md => README.md +38 -0
@@ 8,6 8,44 @@
GLib lacks a good way to represent an optional type, so this library provides
an optional type to use in conjunction with GLib.

## Purpose

This library is kind of theoretical. It stemmed from me writing an API wrapper
using GLib. API requests typically have notions of nullability whether it is
request bodies, response bodies, or query parameters. GLib by itself does not
include a good way to have include nullable types. Insert `TOption`. I wanted to
see if this could be a good way to implement an optional type for GLib, but it
isn't at least according to the extremely naiive benchmark I wrote which seems
to report `TOption` as 50% to 60% slower than its raw C alternative.

### The Raw C Alternative

The raw C alternative is opinionated I would think, but I have come to the
conclustion that the closest thing to `TOption` functionality is this:

```c
g_autofree int *x = NULL;
if (the_value_exists) {
    x = g_malloc(sizeof(int));
    memcpy(x, &i, sizeof(int));
    g_assert(i == *x);
} else {
    // Do things with NULLability
}
```

Why I believe that this is equivalent functionally is that, in the case of a
none, you simply wouldn't do the `g_malloc/memcpy`, while in the case of
existence, you simply allocate memory and copy the value to it which is
essentially what `TOption` is doing at a higher-level of abstraction. At the
very least this library served as a testbed for an idea, and helped me come up
with a better alternative.

## Lessons Learned

1. Don't take higher-level type systems for granted (Rust for example)
2. Abstractions can bite you in the butt real quick if not used properly

## Usage

```c

A benchmark/benchmark.c => benchmark/benchmark.c +38 -0
@@ 0,0 1,38 @@
#include <string.h>

#include <glib.h>

#include "toption/toption.h"

#define ITERATIONS 10000

int
main(void)
{
	GTimer *timer = g_timer_new();
	g_timer_stop(timer);

	g_timer_start(timer);
	for (int i = 0; i < ITERATIONS; i++) {
		g_autofree int *x = g_malloc(sizeof(int));
		memcpy(x, &i, sizeof(int));
		g_assert(i == *x);
	}
	g_timer_stop(timer);

	gdouble elapsed = g_timer_elapsed(timer, NULL);
	g_print("Using a pointer as an optional type: %f\n", elapsed);

	g_timer_start(timer);
	for (int i = 0; i < ITERATIONS; i++) {
		TOption *o = t_option_from(i);
		g_assert(i == t_option_get_int(o));
		t_option_unref(o);
	}
	g_timer_stop(timer);

	elapsed = g_timer_elapsed(timer, NULL);
	g_print("Using a TOption as an optional type: %f\n", elapsed);

	return 0;
}

R docs/meson.build => benchmark/benchmark.h +0 -0

A benchmark/meson.build => benchmark/meson.build +28 -0
@@ 0,0 1,28 @@
dependencies = [
	libgio_dep,
	toption_dep,
]

c_args = [
	'-DG_LOG_DOMAIN="toption-testing"',
	'-flto',
]

if get_option('buildtype') == 'release'
    c_args += [
        '-DG_DISABLE_CHECKS',
        '-DG_DISABLE_CAST_CHECKS',
    ]
elif get_option('buildtype').startswith('debug')
    c_args += [
        '-DG_ENABLE_DEBUG',
    ]
endif

executable(
	'toption-benchmark',
	'benchmark.c',
	dependencies: dependencies,
	c_args: c_args,
	include_directories: include_directories(join_paths(include_root)),
)

M include/toption/toption.h => include/toption/toption.h +3 -0
@@ 58,4 58,7 @@ gfloat t_option_get_float(TOption *self);
gdouble t_option_get_double(TOption *self);
gpointer t_option_get_pointer(TOption *self);

G_DEFINE_BOXED_TYPE(TOption, t_option, t_option_ref, t_option_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(TOption, t_option_unref)

G_END_DECLS

M lib/toption.c => lib/toption.c +12 -14
@@ 28,8 28,6 @@ struct _TOption
	volatile gint ref_count;
};

G_DEFINE_BOXED_TYPE(TOption, t_option, t_option_ref, t_option_unref)

TOption *
t_option_ref(TOption *self)
{


@@ 99,7 97,7 @@ t_option_contains(TOption *self, const GType type)
TOption *
t_option_none()
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 113,7 111,7 @@ out:
TOption *
t_option_from_boolean(gboolean value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 128,7 126,7 @@ out:
TOption *
t_option_from_char(gchar value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 143,7 141,7 @@ out:
TOption *
t_option_from_uchar(guchar value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 158,7 156,7 @@ out:
TOption *
t_option_from_string(gchararray value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 173,7 171,7 @@ out:
TOption *
t_option_from_int(gint value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 188,7 186,7 @@ out:
TOption *
t_option_from_int64(gint64 value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 203,7 201,7 @@ out:
TOption *
t_option_from_uint(guint value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 218,7 216,7 @@ out:
TOption *
t_option_from_uint64(guint64 value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 233,7 231,7 @@ out:
TOption *
t_option_from_float(gfloat value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 248,7 246,7 @@ out:
TOption *
t_option_from_double(gdouble value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;



@@ 263,7 261,7 @@ out:
TOption *
t_option_from_pointer(gpointer value)
{
	TOption *self = g_try_new0(TOption, 1);
	TOption *self = g_slice_alloc0(sizeof(TOption));
	if (!self)
		goto out;


M meson.build => meson.build +2 -2
@@ 29,6 29,6 @@ subdir('lib')
if get_option('tests')
    subdir('tests')
endif
if get_option('docs')
    subdir('docs')
if get_option('benchmark')
	subdir('benchmark')
endif

M meson_options.txt => meson_options.txt +2 -2
@@ 1,4 1,4 @@
option('tests', type: 'boolean', value: true,
	description: 'Build the test suite')
option('docs', type: 'boolean', value: true,
	description: 'Build the documentation')
option('benchmark', type: 'boolean', value: false,
	description: 'Build the benchmark')

M tests/toption.c => tests/toption.c +33 -33
@@ 8,9 8,9 @@
static void
test_boolean()
{
	gboolean v		 = TRUE;
	TOption *t		 = t_option_from(v);
	const gboolean n = t_option_get_boolean(t);
	gboolean v			 = TRUE;
	g_autoptr(TOption) t = t_option_from(v);
	const gboolean n	 = t_option_get_boolean(t);

	g_assert_true(t && n);
}


@@ 18,9 18,9 @@ test_boolean()
static void
test_char()
{
	gchar v		= TEST_NUMBER;
	TOption *t	= t_option_from(v);
	const int n = t_option_get_char(t);
	gchar v				 = TEST_NUMBER;
	g_autoptr(TOption) t = t_option_from(v);
	const int n			 = t_option_get_char(t);

	g_assert_true(t && n == v);
}


@@ 28,9 28,9 @@ test_char()
static void
test_uchar()
{
	guchar v	   = TRUE;
	TOption *t	   = t_option_from(v);
	const guchar n = t_option_get_uchar(t);
	guchar v			 = TRUE;
	g_autoptr(TOption) t = t_option_from(v);
	const guchar n		 = t_option_get_uchar(t);

	g_assert_true(t && n == v);
}


@@ 38,9 38,9 @@ test_uchar()
static void
test_string()
{
	gchararray v	   = TEST_STRING;
	TOption *t		   = t_option_from(v);
	const gchararray n = t_option_get_string(t);
	gchararray v		 = TEST_STRING;
	g_autoptr(TOption) t = t_option_from(v);
	const gchararray n	 = t_option_get_string(t);

	g_assert_true(t && n == v);
}


@@ 48,9 48,9 @@ test_string()
static void
test_int()
{
	gint v		 = TEST_NUMBER;
	TOption *t	 = t_option_from(v);
	const gint n = t_option_get_int(t);
	gint v				 = TEST_NUMBER;
	g_autoptr(TOption) t = t_option_from(v);
	const gint n		 = t_option_get_int(t);

	g_assert_true(t && n == v);
}


@@ 58,9 58,9 @@ test_int()
static void
test_int64()
{
	guint64 v		= TEST_NUMBER;
	TOption *t		= t_option_from(v);
	const guint64 n = t_option_get_uint64(t);
	guint64 v			 = TEST_NUMBER;
	g_autoptr(TOption) t = t_option_from(v);
	const guint64 n		 = t_option_get_uint64(t);

	g_assert_true(t && n == v);
}


@@ 68,9 68,9 @@ test_int64()
static void
test_uint()
{
	guint v		  = TEST_NUMBER;
	TOption *t	  = t_option_from(v);
	const guint n = t_option_get_uint(t);
	guint v				 = TEST_NUMBER;
	g_autoptr(TOption) t = t_option_from(v);
	const guint n		 = t_option_get_uint(t);

	g_assert_true(t && n == v);
}


@@ 78,9 78,9 @@ test_uint()
static void
test_uint64()
{
	guint64 v		= TEST_NUMBER;
	TOption *t		= t_option_from(v);
	const guint64 n = t_option_get_uint64(t);
	guint64 v			 = TEST_NUMBER;
	g_autoptr(TOption) t = t_option_from(v);
	const guint64 n		 = t_option_get_uint64(t);

	g_assert_true(t && n == v);
}


@@ 88,9 88,9 @@ test_uint64()
static void
test_float()
{
	gfloat v	   = TEST_NUMBER;
	TOption *t	   = t_option_from(v);
	const gfloat n = t_option_get_float(t);
	gfloat v			 = TEST_NUMBER;
	g_autoptr(TOption) t = t_option_from(v);
	const gfloat n		 = t_option_get_float(t);

	g_assert_true(t && n == TEST_NUMBER);
}


@@ 98,9 98,9 @@ test_float()
static void
test_double()
{
	gdouble v		= TEST_NUMBER;
	TOption *t		= t_option_from(v);
	const gdouble n = t_option_get_double(t);
	gdouble v			 = TEST_NUMBER;
	g_autoptr(TOption) t = t_option_from(v);
	const gdouble n		 = t_option_get_double(t);

	g_assert_true(t && n == TEST_NUMBER);
}


@@ 108,9 108,9 @@ test_double()
static void
test_pointer()
{
	gpointer v		= &test_pointer;
	TOption *t		= t_option_from(v);
	gconstpointer n = t_option_get_pointer(t);
	gpointer v			 = &test_pointer;
	g_autoptr(TOption) t = t_option_from(v);
	gconstpointer n		 = t_option_get_pointer(t);

	g_assert_true(t && n == test_pointer);
}