Rants from the Ballmer Peak rss feed

Writing C libraries with Nim

Context

When you look at reStructuredText tool support you can notice that with the main reference implementation being written in Python, all other implementations are in languages equal or greater than Python: Java, Scala, Haskell, Perl, PHP, Nim. All these languages have in common that the programmer doesn't have to manage memory manually, and given the complexity of reStructuredText that doesn't seem to be a coincidence. This may have slowed down adoption of reStructuredText compared to Markdown. Markdown started as a Perl script, but its simplicity led to many C libraries, and even a standarization attempt, not without typical drama. Wouldn't it be nice to have a C library for reStructuredText?

Since I dislike Python due to its brittleness and slow speed, I didn't want to use a Python solution for an OS X Quick Look plugin. I wrote quicklook-rest-with-nim which just takes the work done by the Nim developers in the rst, rstast and rstgen modules and packages it as a Quick Look renderer. Everything is statically linked, you can copy the plugin to any machine and it should run without any other runtime dependencies (note: some unknown bug prevents it from working on Yosemite when installed in a home directory, but works fine form a system folder).

The Quick Look renderer is implemented using the default Objective-C Xcode template modifying it to call the Nim code through C bindings. That's when I realised the Nim implementation could be distributed as a plain C library for other languages to use, to avoid their pain rewriting the wheel or running shell commands. For the Quick Look plugin I was simply using two entry points exported to C with hard coded values, not acceptable to other people. I started then to move the custom Nim code to a separate module named lazy_rest. Exposing directly the Nim API didn't make sense for several reasons, so first I implemented a slightly different which I think is nicer than the original, then proceeded to wrap it in a separate C API module which is really another Nim file wrapping all of its procs with the exportc pragma.

The project was successful, when I replaced the old rester module with lazy_rest I dropped several brittle shell scripts and external nim compiler invocations from the project and simply dragged the C files into the Xcode project. This was pleasantly easy. The refactoring of the original reStructuredText modules into lazy_rest wasn't that easy though, I did hit some problems or annoyances. This post is going to enumerate the issues I found, in case you would like to make some other Nim module available to C users.

Namespaces and identifiers

Nim compiles to C, but most of the identifiers will have mangled C names you usually don't care about. Looking at the code found in the nimcache directory you can get an idea of the mangling pattern (which is in any case not part of the public API and subject to change with each Nim version):


N_NIMCALL(NimStringDesc*, saferststringtohtml_235288)(
N_NIMCALL(NimStringDesc*, saferstfiletohtml_235656)(
N_NIMCALL(NimStringDesc*, sourcestringtohtml_235723)(

The mangling is done to avoid having linker errors due to two symbols being named the same. Especially necessary for Nim where you can overload procs or have two procs named the same living in separate modules. When you use the exportc pragma the compiler won't mangle the name, so you have to pick a good unique one. The API of lazy_rest is really small, but still I decided to use the typical Objective-C pattern of prefixing all symbols with two letters.

Memory handling

Memory handling was obviously going to be a problem. C is managed manually, Nim has a garbage collector. Language bindings for any programming language always have these issues when the memory management is different, especially since the languages communicating are usually not aware of each other. Memory passed in from C to Nim is just a cstring, that's fine because it can be converted to a Nim string. However, what do we do with a Nim proc which returns a string to C? Strings in Nim are implicitly convertible to cstring for convenience of C bindings, but what happens to their memory? Who handles that?

Stuff is hard

The manual mentions the built in procs GC_ref() and GC_unref() can be used to keep the string data alive. That means that the C code calling this API would have to know about freeing the memory too. Instead I decided to store the result in a global variable. This forces the string to not be freed even when calling other Nim code which could trigger a garbage collection, and it is easier on the C programmer for the common use of one shot reStructuredText transformations. Improvements can be reviewed in the future whenever lazy_rest gains a user base greater than one (me).

One thing worth mentioning here too is that conversions between string and cstring are not always correct. A nil string won't convert to a nil cstring. One way to deal with this is wrapping the string to cstring conversion to check explicitly for nil.

Exporting types from the Nim standard library

Part of the configuration/input options of lazy_rest are passed in through a StringTableRef. These type was named PStringTable in Nimrod 0.9.6, and unfortunately it is not possible to export such symbols. The typical usage of this type is to store configuration options from a file or memory string, so instead I provided lr_set_global_rst_options(). C users can create an in memory string with the necessary configuration options and let the Nim code parse that. Not very optimal, but this is not performance critical. Typically you will call this once before any other reStructuredText generation.

Something which could be a deal breaker for some people writing C libraries is the fact that Nim doesn't export type fields. To work around this limitation you can export setters and getters. If your fields are primitive types this involves an extra function call, which doesn't look very appealing. For Nim types like strings you would have to implement the setters and getters anyway. The lazy_rest API I export is mostly an opaque render-and-forget approach to the many internal types used for parsing and rendering, so it wasn't a problem.

Export enums and constants

The Nim language doesn't allow exporting enums or consts to C. This is quite a bummer. For lazy_rest I did add the lconfig module which contains several constants mapping to strings used for StringTableRef configuration objects. C users have to look at the documentation and duplicate their own hard coded strings.

I suggested at some point adding an emit header pragma. This pragma would work in a similar way to the emit pragma but instead of generating C code it would allow you to add lines to the final header generated by the Nim compiler. With such pragma I could write a macro to wrap all those constants and let them pass through to the compiler while at the same time generating extra header lines.

Recently Nim 0.10.2 was released and it also provides a way to write to a file from a macro. Macros happen at compile time, likely before any C header is generated, but I think a band aid for this issue could be to generate manually an additional C header in memory and write it to the nimcache directory. Maybe in the future I'll try this.

Errors and exception handling

Plus there is no API

Exceptions are something else C doesn't have. Nim procs like rst_string_to_html() will throw exceptions on error, so how does the C binding deal with that? The C API module uses Nim's effect system for exception tracking. All the procs are annotated with the {.raises: [].} pragma. This pragma tells the compiler that no exception should be raised out of the proc, if there is any potentially being raised the code won't compile, and you have to add the appropriate try/except combo somewhere to appease the compiler.

Annotating procs with this pragma was very satisfying because after doing so you realise how much stuff could potentially break. In other languages you are left with the uncertainty that something could break and you have no catch for it, which leads to typical catch-all blocks in several points of the code, whether they are necessary or not. In Nim by default this could happen too, but the empty raises pragma helps you go through each possible error.

Thanks to this pragma I am confident there won't be any exception leaving the Nim domain. Such exceptions are treated for the C API as functions returning NULL instead of the expected value. The errors are again stored in another Nim global variable, and you can retrieve them with helper functions ending in _error like lr_rst_string_to_html_error().

Callback exception tracking

Things get trickier with exception tracking when you involve callbacks. The reStructuredText parser does have a callback to report warnings and errors to the user. This callback can just echo information to the user, but it can also raise an exception, aborting parsing. So you have a proc which uses a callback, and the proc itself has been protected with all sort of try/except blocks to keep the callback from causing trouble. The Nim compiler however disagrees, see this little snippet of code extracted from an issue I created:

proc noRaise(x: proc()) {.raises: [].} =
  try: x()
  except: discard

proc callbackWichRaisesHell() {.raises: [EIO].} =
  raise newException(EIO, "IO")

proc use() {.raises: [].} =
  # doesn't compile even though nothing can be raised!
  noRaise(callbackWichRaisesHell)
This code looks and reads perfectly fine to me. Despite passing callbackWichRaisesHell around, the noRaise() proc won't ever raise anything, but the example won't compile. It will compile if you add a wrapper layer around the callback, as Araq suggests in the GitHub issue, or if you remove the empty raises pragma from the use() declaration (but that was the point of using the pragma). The reported issue was closed, meaning it's OK to have to patch correct code. I don't know yet if patching good code being the correct answer to a problem is more sad than having a compiler unable to reason about a ten line program.

In any case this wasn't a problem for the library, since I wanted the callbacks to be usable from C there wasn't any point in making them raise exceptions (how would you raise a Nim exception from C code?). I simply modified the TMsgHandler callback type to raise nothing and instead return the possible error as a non nil string. This avoided the problem of callbacks raising any exceptions.

Pig and elephant DNA just won't splice, so know also that callbacks and exception tracking have issues together.


Threads

Threads are terrible

Parsing and generating HTML from text is pretty much sequential, you can't start generating HTML for a random part of the document because the previous part could modify its meaning. But we have multi processor machines everywhere, so I thought it would be nice to provide a queue like API where you pass all the files or strings you need to process (e.g. results of scanning the file system) and let the multiple processors do their job, returning all the results.

I started the lqueues module but couldn't get much done so I've left it disabled. I've done threading in C, Java, Objective-C, and after the initial problems grasping deadlocks and race conditions, nowadays I seem to be able to write at least non crashing code. But I couldn't get Nim to do the same. My biggest gripe was with the fact that threads can't touch other thread's variables, so they have to communicate through shared globals. Or use channels/actors which presumably are not the right solution (couldn't get the expected performance gains from them, but at least they didn't crash).

Now that Nim 0.10.2 has been released there is hope in the new parallel and spawn statements, so I should try that soon. Still, I don't understand what's the presumable benefit of having threads unable to mutate state from other threads. To me it seems more like it's easier to implement concurrency with immutable state, but then, all the other languages I've worked with have mutability and they work perfectly fine.

I don't think it's coincidence that there is pretty much zero Nim threaded code out there being written outside of a few very specific cases. Again, not something I'm worried now, but raises some questions for future work. At the moment I can't see myself using Nim for GUI programming because all the asynchronous patterns I know work with explicit mutability in mind. Neither the new parallel and spawn statements nor async seem to be oriented for GUI programming where you require callbacks for progress indication (and this has to happen on the main thread, aka GUI thread) or cancellation. Time to learn new tricks I guess, maybe Nim is just so superior in this area I'm unable to see the benefits yet. <insert needs-enlightment-here>


Conclusion

From the point of view of C library consumers, this project mostly works and is viable. Users can go to the lazy_rest releases section, download the pre generated C sources packages and use without having to install or even know about Nim. For generic C API libraries only the exportation of enums, constants and type fields seems to be a glaring problem because mostly everybody will hit it. Fortunately it doesn't seem to be hard to fix. As more Nim users try to export their Nim code with a C API there will be more interest in fixing or improving these issues. And maybe in the not so distant future it will make sense to use Nim as a perfect replacement for C when you want to write reusable libraries for C users, or other languages using C bindings.

$ nim c -r complex_callbacks.nim
complex_callbacks.nim(9, 21) Info: instantiation from here
complex_callbacks.nim(6, 41) Error: can raise an unlisted exception: IOError

See the article index or browse articles by tags: languages , programming , nim .
Published on: 14/01/2015 22:59. Last update: 10/09/2016 18:58. rss feed
Copyright 2021 by Grzegorz Adam Hankiewicz.
Generated with ipsum genera. Look at the source code.