$darkmode
Elektra 0.11.0
|
Key
and KeySet
The structs for Key
and KeySet
are opaque, i.e., only the typedef
s are part of the public headers, the actual struct
definitions are in a private header. Because of that, libelektra-core
must provide a way to construct a new Key
or KeySet
.
However, since both Key
and KeySet
are rather complex structures consisting of multiple parts (name, value, metadata, and collection of Key
s respectively), it is not straightforward to create the best API for these functions.
malloc
and memcpy
calls should be kept to a minimum and temporary allocations should be avoided.libelektra-core
, but user code is expected to use builders rather than calling constructors directly.Key
, including name, value and metadata, or even whole KeySet
in a single call. We assume these cases overlap almost entirely, with the cases where we expect builder functions to be used. Therefore, it doesn't matter much, if the constructor functions don't allow this can of Key
/KeySet
construction and extra calls to e.g., keySetValue
are needed.The constructor functions are just specialized allocators:
While this would work decently well for ksNew
(don't allocate array, only allocate array on first Key
insertion), there is an issue for keyNew
. A Key
must have a name. Therefore, keyNew
must set some default name in the Key
it returns.
Even if there was a suitable default name, it would still be wasteful, since in many (almost all) cases, the name will soon be replaced via a keySetName
call.
A clear advantage of this option is that the very simple API means the functions are callable without issue from basically any language.
Instead of taking no arguments at all, the constructor functions take the minimal number of arguments:
This solves the default name issue for keyNew
. For ksNew
there was no issue and the alloc
parameter isn't strictly speaking needed, but it can still be helpful. For example, if the caller knows they will insert 100 keys, the can call ksNew (100)
to avoid later allocations to resize the array in the KeySet
.
The API is also still simple enough that it can be called from any binding.
Depending on the rest of the libelektra-core
API, it may make sense to use a public struct to bundle the arguments of keyNew
:
Whether the struct would be passed by value or as a pointer also depends on the rest of the API.
An important distinction between Key
and KeyName
in this solution is that Key
can be seen as more of class, while KeyName
is just a bundle of fields. That is why Key
is opaque and KeyName
would be public. The fields of a Key
are implementation details, but KeyName
is just a kind of alias for its fields.
Lots of Key
s will have a value from the moment they are created, e.g., meta:/
keys are rarely created without a value. Therefore, it might make sense if keyNew
took an optional (=nullable) argument for the value of the key:
Note: Because this solution is much easier with the bundle structs, we use them here. It would work without them as well, but we'd need two optional arguments (pointer and size) for the value. Similarly, passing the bundle struct by value would mean you have to pass
(KeyValue){ .value = NULL, .size = 0 }
instead of justNULL
.
For KeySet
, we can pass a list of Key *
to initialize the KeySet
with:
This API is easy to call in C:
However, many languages for which we provide bindings can't use variadic arguments. So it would make more sense to have a function like this only in a C-specific library (libelektra-lowlevel-c
), and have the version from above in libelektra-core
.
An alternative would be to use an array argument:
This is easier to call from other languages, but it's slightly more cumbersome in C:
The array parameter also has some other advantages. We could copy it with a single memcpy
and use something like qsort
, instead of copying the Key *
one by one from the variadic arguments. Also, a Key *[]
provides more type safety compared to a variadic arguments.
For ksNew
the above solution already uses the full set of arguments to initialize a KeySet
fully.
For Key
we'd also need to take metadata.
Note: This solution is described only for completeness's sake. We assume that Builder Functions for
Key
andKeySet
builder functions"" exist outside oflibelektra-core
. With this solution those builders would be superfluous.
This could be done by using a system of variadic arguments, called like so:
However, as discussed above, such functions are hard to call from many other languages. You also loose type safety in C and the function is not particularly intuitive to use. To emphasize this last point, consider that the signature for the above keyNew
would likely be:
Things can be slightly improved, by passing the keynames as a single string, but it still a bad API, with lost of potential for misuse.
A better option would be to build on the bundle structs option from above, by adding an new struct for metadata:
This could be called as:
Suggestion: Minimal Arguments
libelektra-core
"Minimal Arguments" is enough. The more complex APIs can be provided as builder functions in other libraries.libelektra-core
to construct Key
s and KeySet
s in a single call.