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);
}