getting-started.md 25 KB
Newer Older
John Croisant's avatar
John Croisant committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Getting Started with Jiffi

This guide provides broad, high-level information to help you learn
how to use Jiffi. See the
[`jiffi` egg docs on the CHICKEN wiki](https://wiki.call-cc.org/eggref/5/jiffi)
for detailed API reference documentation.

Table of contents:

[[_TOC_]]


## Overview of Jiffi features

Jiffi provides features to handle many different aspects of creating
foreign function interface (FFI) bindings to a C library:

* [Functions and Macros](https://wiki.call-cc.org/eggref/5/jiffi#functions-and-macros):
John Croisant's avatar
John Croisant committed
19
  Create bindings to C functions, C macros, or strings of custom C
John Croisant's avatar
John Croisant committed
20
21
  code, allowing you to use them as Scheme procedures.
* [Constants and Enums](https://wiki.call-cc.org/eggref/5/jiffi#constants-and-enums):
John Croisant's avatar
John Croisant committed
22
23
24
25
26
  Access number and string constants provided by the C library.
  Convert between integers and [enum symbols](#enum-symbols) (or lists
  of symbols, for flags and bitmasks). The values of the constants and
  enums are determined automatically, so you don't need to hardcode
  the values into your Scheme code.
27
28
29
30
31
32
33
34
35
36
37
38
* [Armor](https://wiki.call-cc.org/eggref/5/jiffi#armor):
  Define Scheme types that wrap the bare data (pointers, etc.) of C
  structs, unions, and arrays.
  See "[Why should I use armor?](#why-should-i-use-armor)", below.
* [Structs and Unions](https://wiki.call-cc.org/eggref/5/jiffi#structs-and-unions):
  Allocate, free, and copy struct/union instances, either wrapped in
  armor or as [bare data](#warning-about-bare-data). Get and set
  struct/union fields, with optional conversion/validation of data.
* [Arrays of Structs and Unions](https://wiki.call-cc.org/eggref/5/jiffi#arrays-of-structs-and-unions):
  Allocate and free arrays, either wrapped in armor or as bare data.
  Get and set array items, either wrapped in armor or as bare data.
  Map or iterate over all items in the array.
John Croisant's avatar
John Croisant committed
39
40
41
42
43
44
45
46
47


## Examples

A thorough example can be found in the
[tests directory](https://gitlab.com/jcroisant/jiffi/tree/main/tests):

* [`libfoo.scm`](https://gitlab.com/jcroisant/jiffi/blob/main/tests/libfoo.scm): example bindings to a made-up C library
* [`libfoo.h`](https://gitlab.com/jcroisant/jiffi/blob/main/tests/libfoo.h): the made-up C library
John Croisant's avatar
John Croisant committed
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84


## Macro style

> **Section summary:** Most of Jiffi's macros use a consistent style.

Most of Jiffi's features are macros, and most of those macros follow
this interface style:

```
(macro-name thing-name
 (prerequisite ...)
 keyword: value
 ...
 variadic-arg
 ...)
```

Macros that define a single thing (procedure, record type, etc.) have
a `thing-name` as the first argument. If the macro has a `thing-name`
argument, it is always required. Macros that define multiple things,
or that don't define anything, do not have a `thing-name` argument.

The `prerequisites` are things that will be used in the macro body,
and usually need to be defined before calling this macro. Typically
they are either literal values like symbols and strings, or the names
of existing procedures. You must provide all prerequisites shown in
the documentation, in the correct order.

The `keyword: value` clauses are used to specify options that affect
the behavior of the macro, or to specify the names of new procedures
to be defined. Unless the macro's documentation says otherwise, every
keyword clause is required. They can be in any order, but they must
appear after the prerequisites. Jiffi will print a warning at macro
expansion time if a duplicate or unrecognized keyword is provided,
because that usually indicates a bug in your code.

John Croisant's avatar
John Croisant committed
85
86
Some macros have `variadic-args`, where the macro accepts a
potentially unlimited number of additional arguments. For example,
John Croisant's avatar
John Croisant committed
87
88
89
90
91
[`define-foreign-values`](https://wiki.call-cc.org/eggref/5/jiffi#define-foreign-values)
accepts an unlimited number of constants,
[`define-struct-accessors`](https://wiki.call-cc.org/eggref/5/jiffi#define-struct-accessors)
accepts an unlimited number of struct fields, and
[`define-binding**`](https://wiki.call-cc.org/eggref/5/jiffi#define-binding-2)
92
93
accepts an unlimited number of lines of C code. Variadic args must
appear after the prerequisites and keyword clauses, if there are any.
John Croisant's avatar
John Croisant committed
94
95


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
## Why should I use armor?

> **Section summary:** Jiffi uses record types called "armor" to wrap
> the bare data of C structs, unions, and arrays. This greatly
> improves safety, makes debugging easier, and provides extra
> functionality.

The C programming language is notoriously difficult to use safely.
Programmers must be very careful to avoid bugs such as memory leaks,
buffer overflows, memory access violations, uninitialized data,
use-after-free errors, and double free errors. These types of bugs can
cause the software to have incorrect behavior, crashes, or even
serious security vulnerabilities.

Writing correct and safe code is even more difficult when using a C
library via a foreign function interface, because pointers (as well as
[locatives](https://wiki.call-cc.org/man/5/Module%20(chicken%20locative))
and [blobs](https://wiki.call-cc.org/man/5/Module%20(chicken%20blob))
in CHICKEN) do not have any C type information, so the C compiler
cannot check that you are passing the correct type of arguments to a
function.

To help protect against common types of bugs, and to provide a more
Scheme-idiomatic interface to C data, Jiffi provides a way to define
new Scheme types that hold bare C data. These Scheme types are called
"armor", because they wrap the bare data in a protective layer.

You can define an armor type with
[`define-armor-type`](https://wiki.call-cc.org/eggref/5/jiffi#define-armor-type).
It is recommended that you define a separate armor type for each C
struct, union, or array type in the C library you are binding, so that
you can use [type checking](https://wiki.call-cc.org/man/5/Types) and
predicate procedures to distinguish between C types in Scheme.

Behind the scenes, each armor type is simply a
[record type](https://wiki.call-cc.org/man/5/Module%20(chicken%20base)#define-record-type)
with a specific layout and helper procedures that make it safe and
useful for holding C data. The armor type has a slot (aka field) to
hold the bare data (a pointer, locative, or blob of the data bytes),
one or more private slots used internally by Jiffi, and optionally any
extra slots that you define.

Using armor provides many benefits of safety, ease of debugging, and
convenience:
John Croisant's avatar
John Croisant committed
140
141

* Bindings can check at run time that the correct argument type was
142
  passed to a procedure, making it easier for users to find bugs in
John Croisant's avatar
John Croisant committed
143
  their code, instead of having strange behavior or crashes with no
144
145
146
147
  useful error message.
* For procedures that have type declarations, the CHICKEN compiler can
  warn at compile time if the procedure is called with the wrong
  argument type, so users can find bugs in their code even faster.
John Croisant's avatar
John Croisant committed
148
* You can optionally use memory management to automatically free the
149
150
151
152
153
  data when it is no longer being used, to avoid memory leaks. Also,
  armor protects against accidentally using the data after it has been
  freed, which could cause a crash or security vulnerability, and
  against freeing the data multiple times, which could cause a crash.
* Armors can have a
John Croisant's avatar
John Croisant committed
154
  [parent-child relationship](https://wiki.call-cc.org/eggref/5/jiffi#armor-parent-set),
155
156
157
158
159
160
161
162
163
  for cases where one armor (the child) wraps part of the data of
  another armor (the parent). For example, armors returned from some
  [array accessors](https://wiki.call-cc.org/eggref/5/jiffi#define-array-accessors)
  are children of the array armor. This protects from accidentally
  freeing the child's data, which could cause a crash, or free all of
  the parent's data. Also, the child will automatically be nullified
  when the parent is freed, to protect against using the parent's data
  after it has been freed, which could cause a crash or security
  vulnerability.
John Croisant's avatar
John Croisant committed
164
* You can use
165
  [`define-armor-printer`](https://wiki.call-cc.org/eggref/5/jiffi#define-armor-printer)
John Croisant's avatar
John Croisant committed
166
167
168
  to show useful information about the struct or array when it is
  printed in error messages and the REPL, which makes debugging
  easier.
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
* Armor types can have extra slots to hold additional data or
  metadata, besides what is stored in the struct/union/array itself.

The downside of using record instances is that they use additional
memory and CPU time, which can negatively affect performance. The next
section describes the performance costs of armors, and suggestions of
how to optimize performance.


## Optimizing armor performance

> **Section summary:** Using armor has a performance cost, so you may
> want to use bare data locally in performance-critical areas of your
> software.

Armor has many benefits (described in the previous section), but using
armor requires more memory and CPU time than using bare data.
Specifically:

188
* Each armor instance uses at least 70 bytes of memory, not including
189
190
191
192
193
  the memory of the bare data.
* Additionally, each armor instance uses 8 bytes per extra slot in the
  armor type, not including the memory of the value in the slot.
* Additionally, if an armor instance is
  [thread-safe](https://wiki.call-cc.org/eggref/5/jiffi#armor-thread-safety),
194
195
196
197
198
199
200
201
  it uses approximately 100 bytes for the armor lock.
* Additionally, if an armor instance
  [tracks its children](https://wiki.call-cc.org/eggref/5/jiffi#armor-tracks-children),
  it temporarily uses approximately 30 bytes per active child armor
  for bookkeeping, not including the memory of the child armor. The
  bookkeeping memory is reclaimed when the child armor is nullified.
* Armors that track their children use some CPU time to perform
  bookkeeping when
John Croisant's avatar
John Croisant committed
202
  [`armor-parent-set!`](https://wiki.call-cc.org/eggref/5/jiffi#armor-parent-set)
203
204
  is called, when the child armor is nullified, and when the parent
  armor is nullified.
205
206
* Armors that track their children *and* are thread-safe use some
  memory and CPU time to spawn threads to perform bookkeeping.
207
208
209
210
211
212
213
214
215
216
217
* Wrapping and unwrapping armor instances uses some CPU time.

This performance cost is usually only noticeable if you create a very
large number of armors, or if you unwrap armors very often, for
example within an inner loop. If you experience performance issues in
those situations, try using bare data locally in the
performance-critical areas of your software. For example:

* If you will be accessing a struct or union repeatedly within a
  procedure or loop, try unwrapping the armor once, then using the
  bare pointer repeatedly.
218
219
220
* If you will be repeatedly getting children of an armor, try
  disabling children-tracking on the parent armor to significantly
  reduce bookkeeping.
221
222
223
224
225
226
227
228
229
230
231
232
233
234
* If you are mapping or iterating over a large array, try using an
  [array item pointer mapper](https://wiki.call-cc.org/eggref/5/jiffi#array-item-pointer-mapper)
  or [array item pointer iterator](https://wiki.call-cc.org/eggref/5/jiffi#array-item-pointer-iterator)
  to get bare pointers for each array item. This improves performance
  in three ways: no armors are created for the array items, the items
  don't need to be unwrapped to use them, and there is no bookkeeping
  to create parent-child relationships for the items.
* If you are temporarily creating many structs, unions, or arrays
  within a self-contained part of your code, try allocating bare blobs
  instead of making armors.
  See "[Which allocator should I use?](#which-allocator-should-i-use)".

If you do use bare data, you must be careful to avoid the pitfalls
described in the next section.
John Croisant's avatar
John Croisant committed
235
236
237
238


## Warning about bare data

239
240
241
242
> **Section summary:** You must be very careful when using pointers,
> locatives, or blobs that are not wrapper in armor. Using bare data
> incorrectly can cause bugs, crashes, and security vulnerabilities in
> your software.
John Croisant's avatar
John Croisant committed
243

244
245
246
Most procedures and bindings that accept an
[armor](#why-should-i-use-armor) as an argument will also accept a
bare (i.e. not wrapped in armor) pointer,
John Croisant's avatar
John Croisant committed
247
[locative](https://wiki.call-cc.org/man/5/Module%20(chicken%20locative)),
248
249
250
251
or [blob](https://wiki.call-cc.org/man/5/Module%20(chicken%20blob)).
This allows users to optimize performance when necessary, because
pointers, locatives, and blobs have less memory and CPU overhead than
armors (see previous section).
John Croisant's avatar
John Croisant committed
252
253
254
255
256

However, pointers, locatives, and blobs must be used with caution.
Misusing them can cause memory leaks, unexpected behavior, crashes, or
security vulnerabilities in your software:

257
* Type checking is not performed, so you must be careful not to pass
John Croisant's avatar
John Croisant committed
258
259
260
261
262
  the wrong type of data to a function. For example, if a function
  accepts two pointer arguments of different types, and you pass them
  in the wrong order, it may crash with no clue about what is wrong.
* When using pointers (not locatives or blobs), you have to manually
  manage memory. If you don't free the memory, there might be a memory
263
264
  leak. But if you free the memory too soon and try to use it later,
  the software can crash or have security vulnerabilities.
John Croisant's avatar
John Croisant committed
265
266
267
268
269
270
271
272
* Debugging is more difficult because error messages don't provide
  very much useful information. For example, the message might only
  say `#<pointer 0x1234567890>`. Or, the software might just crash
  with a message about "memory access violation" or "segmentation
  fault", without even telling you what part of your code caused the
  crash.

For those reasons, it is recommended to use record instances most of
273
274
the time, and only use bare data locally in performance-critical areas
of your software, as described in the previous section.
John Croisant's avatar
John Croisant committed
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295


## Which allocator should I use?

> **Section summary:** Jiffi provides many ways to allocate C structs,
> unions, and arrays. Start with `make/af` (`MAKE/AUTOFREE`), then
> switch to another allocator if you have performance problems caused
> by allocating a very large number of objects.

Jiffi offers many kinds of
[struct allocators](https://wiki.call-cc.org/eggref/5/jiffi#define-struct-allocators)
and
[array allocators](https://wiki.call-cc.org/eggref/5/jiffi#define-array-allocators)
to choose from, each with its own advantages and disadvantages.

If you are a beginner, just use `make/af` (i.e. `MAKE/AUTOFREE`).
It is the safest, most fool-proof, and easiest to debug. However, it
has worse performance than the other allocators, especially if a very
large number of structs/unions/arrays are being allocated and
discarded (e.g. hundreds per second). This is because it must
[set a finalizer](https://wiki.call-cc.org/man/5/Module%20(chicken%20gc)#set-finalizer)
296
297
on every armor instance, to automatically free the C memory when the
armor instance is garbage collected. The CHICKEN garbage collector
John Croisant's avatar
John Croisant committed
298
299
300
301
302
303
304
305
306
307
308
309
310
311
will get overwhelmed and sluggish if there are a very large number of
finalizers.

Later, you may want to switch to a different allocator to optimize
performance. Which allocators are best to define (as a library author)
or to use (as a library user)? It depends on your situation. Here are
some factors to consider:

* **Typed?:** Does the object contain type information? In other
  words, will it provide useful feedback if you pass the wrong type of
  argument to a procedure?
* **Managed?:** Does the object perform automatic memory management?
  In other words, will the memory automatically be freed during
  garbage collection, to prevent memory leaks? Or do you have to
312
  manually free the memory when you are done using the object?
John Croisant's avatar
John Croisant committed
313
314
315
316
* **Static?:** Will the memory address always stay the same, or might
  it change when the garbage collector runs? A static address makes it
  safe to store a pointer to the memory inside another struct, or to
  pass it to libraries that store the pointer and use it later.
317
318
319
* **Armor?:** Does it return an armor instance? In other words, can it
  have extra slots for metadata, and an
  [armor printer](https://wiki.call-cc.org/eggref/5/jiffi#define-armor-printer)
John Croisant's avatar
John Croisant committed
320
321
322
323
324
325
326
  to make debugging easier?
* **Safety:** Overall, how careful do you need to be to avoid crashes
  and security vulnerabilities?
* **Performance:** Overall, how much impact does it have on
  performance (memory and CPU usage, garbage collector performance),
  if you create a very large number of objects?

327
| Name         | Typed?   | Managed? | Static? | Armor?  | Safety      | Performance¹ |
328
329
330
331
332
333
334
335
336
337
338
|-------------:|:--------:|:--------:|:-------:|:-------:|:------------|:-------------|
| `alloc`      | **Yes²** | No       | **Yes** | No      | Very unsafe | Good         |
| `alloc/blob` | No       | **Yes**  | No      | No      | Unsafe      | Very good    |
| `make`       | **Yes**  | No       | **Yes** | **Yes** | Safe        | Okay         |
| `make/af`    | **Yes**  | **Yes**  | **Yes** | **Yes** | Very safe   | Bad          |
| `make/blob`  | **Yes**  | **Yes**  | No      | **Yes** | Safe        | Good         |

¹ Formal benchmarks are not yet available, so the performance here is
really just a guess based on experience.

² `alloc` returns a [tagged pointer](https://wiki.call-cc.org/man/5/Module%20(chicken%20memory)#tagged-pointers),
339
340
341
342
343
which is tagged with the armor name, as a symbol. This makes debug
messages more informative, and you could use it to implement your own
type checking. But, Jiffi unwrappers do not check that a pointer has
the correct tag, so by default there is no feedback if a user passes
the wrong pointer types to a function.
John Croisant's avatar
John Croisant committed
344
345


346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
## When should an armor be a child of another armor?

> **Section summary:** Creating a parent-child relationship between
> armors helps ensure proper cleanup and prevent certain kinds of
> memory-related bugs.

Sometimes two objects (structs, unions, or arrays) are related to each
other, so that one object "owns" some data, and the other object uses
part of the data. In Jiffi, the object that owns the data is called
the "parent", and the object that uses the data is called the "child".

A common example is an array of structs: the array is the parent
because it owns the data, and each struct is a child of the array
because it uses part of the array's data. Another example is a parent
struct with a field that holds another struct by value, or holds a
pointer to another struct that the parent is responsible for freeing.
There are many possible variations of parent and child, but the
important characteristics of a parent-child relationship in Jiffi are:

1. After the parent's data is freed, its children are no longer valid.
2. A child's data must not be freed except by freeing the parent.

Jiffi supports creating
[parent-child relationships](https://wiki.call-cc.org/eggref/5/jiffi#armor-children)
between armors. This helps prevent crashes and security
vulnerabilities caused by use-after-free bugs:

* The parent armor will not be garbage collected while any child armor
  is still using the parent's data (in other words, until all children
  have been garbage collected or
  [nullified](https://wiki.call-cc.org/eggref/5/jiffi#nullify-armor)).
* If the parent armor
  [tracks its children](https://wiki.call-cc.org/eggref/5/jiffi#armor-tracks-children),
  all child armors will be nullified if the parent is manually freed
  or nullified.
* Calling a [struct freer](https://wiki.call-cc.org/eggref/5/jiffi#struct-freer)
  or an [array freer](https://wiki.call-cc.org/eggref/5/jiffi#array-freer)
  on the child will only nullify the child armor, not actually free
  the data. (Trying to free part of the parent's memory could cause a
  crash, or could accidentally free *all* of the parent's memory.)

Internally, creating the relationship modifies the child armor to
store a strong reference to the parent; and, if the parent tracks its
children, modifies the parent armor to store a weak reference to the
child. An armor can have at most one parent, and can have any number
of children.

If you are using multiple SRFI-18 threads, you may need to enable
thread safety on some parent armors, as described in the next section.


397
398
## When should I enable armor thread safety?

399
400
401
> **Section summary:** If you use multiple SRFI-18 threads to call
> certain procedures on an armor that tracks its children, you should
> enable thread safety to prevent a race condition.
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469

If you use multiple
[SRFI-18](https://wiki.call-cc.org/eggref/5/srfi-18) threads in your
software, you may need to enable thread safety on some armors, to
prevent a race condition that may cause Jiffi's internal bookkeeping
to become incorrect. If that happens, armors that
[track their children](https://wiki.call-cc.org/eggref/5/jiffi#armor-tracks-children)
may not be correctly cleaned up. This can lead to use-after-free bugs
if you try to use the children after the parent armor has been freed,
which can cause crashes or security vulnerabilities in your software.

You should enable thread safety if multiple threads might call any of
the procedures listed below on the same armor at the same time, *and*
the armor tracks its children. You do not need to enable thread safety
if only one thread will access the armor at a time, *or* if you do not
use any of the procedures listed below, *or* if the armor does not
track its children, *or* if the armor will never have children. Thread
safety uses additional memory and CPU time, so it is recommended only
when necessary.

The following procedures can potentially cause the race condition:

* [`armor-parent-set!`](https://wiki.call-cc.org/eggref/5/jiffi#armor-parent-set!)
* [Array item getters](https://wiki.call-cc.org/eggref/5/jiffi#array-item-getter) (aka `ref`)
* [Array item mappers](https://wiki.call-cc.org/eggref/5/jiffi#array-item-mapper) (aka `map`)

You only need to enable thread safety on the parent (or array) armor.
It is not necessary to enable thread safety on the child (or item)
armors.

Enabling thread safety causes the armor to use a lock (aka mutex) to
ensure that only one thread performs bookkeeping with the armor at a
time. See
[`armor-lock-set!`](https://wiki.call-cc.org/eggref/5/jiffi#armor-lock-set)
and
[`make-armor-lock`](https://wiki.call-cc.org/eggref/5/jiffi#make-armor-lock).

You can enable thread safety for a single armor instance by calling
`(armor-lock-set! x (make-armor-lock))`. You can enable thread safety
by default for all instances of an armor type by passing
`locking: (make-armor-lock)` to
[`define-armor-type`](https://wiki.call-cc.org/eggref/5/jiffi#define-armor-type).

If you enable thread safety, you must import the `srfi-18` module into
the module that calls `make-armor-lock`. For example:

```
(cond-expand
 (chicken-4 (use srfi-18))
 (else (import srfi-18)))
```

You should also add the `srfi-18` egg to your software's
[dependencies](https://wiki.call-cc.org//man/5/Egg%20specification%20format#dependencies),
to make sure that it will be installed when your software is
installed. (This is not necessary for CHICKEN 4, because it has the
`srfi-18` module built in.)

Be advised that enabling thread safety as described in this section
only prevents race conditions in Jiffi's internal bookkeeping. It does
not prevent race conditions in your software's business logic, for
example caused by modifying the armor's data from multiple threads at
the same time. To prevent other race conditions, you will need to
implement your own solution that is appropriate for your software's
business logic. For example, you could add an extra slot to the armor
type to hold a mutex, and lock the mutex before using the armor.


470
## Why should I use enum symbols?
John Croisant's avatar
John Croisant committed
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497

> **Section summary:** Using symbols instead of integers for enums
> makes code more readable and debugging easier. It's easy to allow
> integer enums too, so users can optimize performance when needed.

Jiffi provides the
[`define-enum-group`](https://wiki.call-cc.org/eggref/5/jiffi#define-enum-group),
[`define-enum-packer`](https://wiki.call-cc.org/eggref/5/jiffi#define-enum-packer),
and
[`define-enum-unpacker`](https://wiki.call-cc.org/eggref/5/jiffi#define-enum-unpacker)
macros to define procedures for converting enums from integers to
symbols, and vice versa. There are many benefits to using enum symbols
instead of integers in your code:

* Symbols make Scheme `case` forms clearer. Variables cannot be
  matched in a `case` clause, because the clause's "data" are not
  evaluated. So, if users want to match an integer enum, they must
  write the integer literal itself. The `case` form will have many
  "magic numbers", which are difficult to understand, brittle, and
  susceptible to bugs caused by typos. If you use symbols, users can
  write the symbol itself, which makes the code clearer.
* Symbols make error messages, debug output, and REPL results more
  informative. If you use integers, messages will not show variable
  names, only integer values, e.g. `#<key-event 106 mods: 192>`.
  To understand this output, users would need to look up which key is
  `106`, and which key modifier bitmasks combine to make `192`.
  Using symbols, the debug output is much easier to understand:
498
  `#<key-event j mods: (rshift lctrl)>`.
John Croisant's avatar
John Croisant committed
499
500
501
502
503
504
505
* Symbols make user code more concise. Instead of writing a long
  variable name like `EVENT_TYPE_SENSOR`, users can write `'sensor`.
  Instead of writing a long expression to combine bitmasks like
  `(bitwise-ior GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT GL_ACCUM_BUFFER_BIT)`
  users can write `'(color depth accum)`.

The downside of using enum symbols is that they have a small amount of
506
507
508
509
510
511
CPU overhead, because symbols must be converted to integers when they
are passed to C functions, and integers must be converted to symbols
when they are returned by C functions. Jiffi's conversion code is
quite efficient, but users may still want to use integers in very
performance critical areas, so you may want to pass `allow-ints: #t`
to `define-enum-group` and `define-enum-packer`.