Skip to content

Instantly share code, notes, and snippets.

@Justasic
Last active December 22, 2022 22:10
Show Gist options
  • Save Justasic/b57bfd05dd8e7a108bc433c8c9a66e59 to your computer and use it in GitHub Desktop.
Save Justasic/b57bfd05dd8e7a108bc433c8c9a66e59 to your computer and use it in GitHub Desktop.
.run() is bad for libraries

.run() is bad for libraries

As a developer I have noticed a trend in libraries where the library developer will write this amazing library that does everything you need it to except that it has a .run() or .start() method. The intent is for the user of said library to do the initialization code in the application entry point and then do something like return app.run();. Whether it's in Python, Java, C++, or Rust they all abstratct away the application's main event loop.

What is an event loop?

An event loop (for those who may be new to programming) will look something like this:

int main()
{
    while (true)
    {
        ProcessSockets();
        ProcessEvents();
        ProcessOtherEvents();
    }
    return 0;
}

It is nothing more than an (optionally conditional) infinite loop which processes events for the application and exists the entire lifetime of the application. Every application has an event loop in order to process events for graphical environment changes, socket events, user input events, or even retrieving data. Most applications will use system calls such as sleep or epoll to slow down the event loop to a frequency. For example, the video game Minecraft uses an event loop that runs at 20 ticks (loop iterations) per second to process events which happen inside the game at that speed.

So what is the problem?

Example:

int main()
{
    app = LibraryAppInterface();
    app.maybeSomeArg("lol");
    app.maybeSomeOption(true);
    return app.run();
}

Applications are not libraries. Period.

No matter how much you try and sell me on "just use this lib with .run()" I will never use it and I will elaborate why:

First, when a "library" abstracts away the event loop, the code is now non-portable with relation to other libraries. I cannot add another library which must have events processed in the event loop without interfacing directly with the first library -- even when the libraries are completely unrelated! I have to read the docs to see how to add something in the first library's event loop interface often via some hack like a 1 second timer -- if the library even has such an interface! Many libraries don't expose an interface for their event loop and I am forced to use threads to spawn 2 separate event loops in the application.

Ewwww Threads!

Yep. Thanks to this design paradigm I now have to handle data synchronization, complex thread mechanics, and debug race conditions -- all of which can be entirely avoided. In addition some libraries don't expect to be running in a threaded environment and may be entirely thread-unsafe so now my scope of work is increasing because I have to debug my code and their library code.

Personally, once I start debugging your library I start to also weigh the cost of using it versus writing my own implementation. Most of the time I find that writing my own library would be better as they expose interfaces for running in a library-external event loop and are much more flexible with the rest of my codebase. There's also the added benefit of being in full control of how the library operates.

So what is the solution?

Your library is not an application, don't write it like one.

Unless you have a legitimately valid reason to have full control over the event loop (aka, GUI libraries like Qt) then you should only be exposing interfaces to the user and instructing them to create their own event loop. Leave the responsibility of correctly processing events to the application developer. They're smart enough to use your library then they're smart enough to know the consqeuences of using it improperly. I would see no problem in providing an optional .run() interface and an "advanced" interface for people like me where the event loop functions (init, loop, and shutdown) are exposed as public APIs. Examples of such APIs can be seen in Microsoft's PeakMessage function for GUI render code.

Exmaple code:

int main()
{
    LibraryA liba("My Library A");
    LibraryB libb(123);

    liba.init("owo some more args maybe?");
    libb.create("ABC");

    while (true)
    {
        liba.loop();
        // here maybe LibraryB optionally handles sleeping the thread
        libb.tick(5); 
    }

    liba.destroy();
    // maybe libb self-destructs with destructor?

    return 0;
}
@ALiwoto
Copy link

ALiwoto commented Jul 14, 2021

Actually it's not this much easy here
It really needs a lot of codes for developers
Also don't you think it will become too dirty?
Like, for different environment developers have to write all different parts (with different behavior)

But yeah, somehow I'm agree, this way developers have more access to their "own application", rather than calling only a sole method from another library and then limiting themselves with their own events call like Update, Draw, etc...


providing an optional .run() interface and an "advanced" interface for people

And I'm agree with this one too, like they can create a .Run() method for running in default and normal mode, but they should provide enough access to necessary methods, members and functions for developers to write their own loop.


Liked this! thanks for writing it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment