Fiber based pseudo-blocking programming model
The key idea behind vibe.d was to take the fast and resource friendly asynchronous I/O model (AIO) and make it comfortable to use. Some other frameworks or toolkits such as node.js directly expose the event based interface of AIO using callback functions. While this is a workable approach, it has several drawbacks.
First and foremost, it often becomes tedious and confusing to write sequences of code (e.g. performing multiple consecutive database queries). Each step will introduce a new callback with a new scope, error callbacks often have to be handled separately. Especially the latter is a reason why it is tempting to just perform lax error handling.
Another consequence of asynchronous callbacks is that there is no meaningful call stack. Not only can this make debugging more difficult, but features such as exceptions cannot be used effectively in such an environment.
The approach of vibe.d is to use asynchronous I/O under the hood, but at the same time make it seem as if all operations were synchronous and blocking, just like ordinary I/O.
What makes this possible is D's support for so called fibers (also often called co-routines). Fibers behave a lot like threads, just that they are are actually all running in the same thread. As soon as a running fiber calls a special
yield() function, it returns control to the function that started the fiber. The fiber can then later be resumed at exactly the position and with the same state it had when it called
yield(). This way fibers can be multiplexed together, running quasi-parallel and using each threads capacity as much as possible.
Using these tools, the event based nature of AIO is completely hidden from the user of the library. It also blends seamlessly with the built-in support for multi-threading. All concurrent operation are done using so called tasks. Every task runs inside of a single fiber and is multiplexed together with other tasks that run at the same time. To the user of the toolkit there is usually no visible difference between a task and a thread.
Having said all that, vibe.d can also be used without any fibers and no event loop if it has to, resembling the standard blocking I/O model.
There is a strong focus on a short and simple API. Common tasks are kept as short and high level as possible, while still allowing fine grained control if needed. By using some of D's advanced features, typical vibe.d based programs reach a level of conciseness that usually only scripting languages such as Ruby can offer.
[work-in-progress] An integrated load balancer is able to dynamically compile, run and test new processes before switching them live after the code has been modified. This allows for seamless and low-risk changes on live-systems.
Exception based error handling
Usually, exception handling usually is restricted to local error handlers in event-based environments - it is impossible to wrap a sequence of events into a try-catch block. vibe.d on the other hand has full support for exception handling.
The fiber feature behaves almost exactly like a thread regarding the stack; a single consistent call stack exists across all events that are handled in sequence. This way it becomes possible to naturally use exception handling, and especially in a web service environment exceptions are the ideal form of error handling.
Exceptions have the extremely useful feature that you can barely ignore them. As such, it is much more difficult to introduce subtle bugs and security holes by unintentionally ignoring error conditions. Any uncaught exceptions will automatically generate an error page and log all useful information in case of HTTP services.
Full integrated web framework
The vibe.d library contains a complete set of tools needed for website and web service development. In addition to a HTTP 1.0/1.1 server, static file serving, an efficient template system, WebSockets, sessions and other features are already integrated. Surrounding features include a markdown filter, MongoDB and Redis drivers, cryptography, natural JSON and BSON support and a simple SMTP client.
There are also means to automatically generate JSON/REST or HTML form based interface from D classes, removing a lot of work and potential for bugs due to avoiding boilerplate code. The REST interface generator supports generation of both, the server and the client end, and as such can be used as a convenient RPC mechanism.
Native database drivers
The core library contains built-in support for MongoDB and Redis databases. These drivers provide a default for fast and flexible data storage. More database drivers are available in the DUB package registry, for example a vibe.d compatible MySQL driver.
Making existing drivers compatible is easy to do thanks to the blocking nature of vibe.d's API. The only thing that has to be done in most cases is to replace the socket calls (
connect() etc.) with the corresponding vibe.d functions. A blog post gives an overview of what needs to be done using the MySQL driver as an example.
Raw TCP and File handling
Raw TCP and file access is of course supported by the toolkit to support custom protocols and file formats. I/O happens through a blocking stream interface that effectively hides the fact that the underlying operations are actually event based.
Asynchronous I/O operations
Instead of using classic blocking I/O together with multi-threading for doing parallel network and file operations, all operations are using asynchronous operating system APIs. By default, libevent is used to access these APIs operating system independently.
Using asynchronous I/O has several advantages, the main point being that the memory overhead is much lower. This is because only a single hardware thread is needed to handle an arbitrary number of concurrent operations. The thread typically rests waiting for events and is woken up by the operating system as soon as new data arrives, a new connection is established, or an error occurs. After each intiated blocking operation (e.g. writing data to a socket), the thread will go back to sleep and let the operating system execute the operation in the background.
Another important aspect for speed is that, because there is only one or a few threads necessary, it is often possible to save a lot of expensive thread context switches. However, note that if the application has to do a lot of computations apart from the actual I/O operations, vibe.d has full support for multi-threading to fully exploit the system's multi-core CPU.
Because using the asynchronous event based model is often more involved regarding the application implementation, fibers are used together with an event based scheduler to give the impression of classic blocking calls and multi-threading, thus hiding the additonal complexities, while retaining the performance characteristics of asynchronous I/O.
Natively compiled code
Vibe.d is written in the D Programming Language. D is a C-style language that compiles down to native machine code with minimal runtime overhead. In addition to being fast, D offers a number of features that allow additional performance improvements and especially code readability, safety and usability improvements compared to other languages such as C++.
Some notable features are listed here, but there are a lot of additional things which would be beyond the scope of this text. See the D feature page or the Programming in D online book for a more general overview.
- Templates, mixins and compile-time functions
The meta-programming system in D is extremely powerful. Templates (comparable to C++ templates) support variadic parameters, as well as string and alias parameters - a feature which enables very comfortable compile-time interfaces.
eval()function, just that the result is statically compiled to machine code (and without the runtime security implications of
These features together with the possibility to read files at compile time have enabled the powerful Diet template parser.
- Garbage collection
The D runtime offers built-in garbage collection, which enables some interesting possibilities, apart from the obvious advantage of no need for manual memory management and the associated risk of memory leaks and dangerous dangling references.
Most notably, it allows to work with immutable data, such as strings or objects, which can be safely referenced and passed between threads without any synchronization overhead. When passing data between threads, it is statically enforced that the data is safe to be referenced from multiple threads. This avoids a whole class of diffucult to reproduce and track down bugs common in multi-threaded code of many other languages.
- Closures and lambdas
A feature also known from some scripting and functional languages, but also C# and a few others are closures and lambdas. They allow to specify callback functions in a very compact and readably way. In turn they tend to have a strong influence on the API design, as they can make computationally efficient or safe APIs bearable to use.
Properties are functions that look like they were a field of a class or struct. They are used throughout the API to get a logical and intuitive interface without all clamps and additional typing needed for normal function calls.
- Module system
D's module system is similar to the one used in Java. Among other things it encodes the dependencies between different modules (D files) in a semantically clear way. Using this information, it becomes possible to build whole applications just by specifying the root file (app.d) on the command line. The dependencies can be found recursively. The rdmd build tool included with the D compiler, which is supported by the DUB package manager, works this way.
- Arrays and slices
Arrays support native slicing with a very natural syntax. Together with the garbage collector they enable extremely fast string parser implementations which are safe (bounds checking) and easy to read at the same time.
- Concise and readable syntax
The syntax in general is very clean and to the point - especially compared to its relative C++. But it doesn't need to fear modern scripting languages such as Ruby and Python in this regard either. Apart from the occasional need to specify an explicit type (types can be inferred using
auto), you will discover that there is not much to miss and that development using D is highly efficient.
Compile-time "Diet" templates
In vibe.d, however, templates can contain embedded D code. The templates are read and parsed while the application is being compiled and optimized D code is generated for them. This means that the runtime overhead for those templates is non-existent - there are no disk accesses, no parsing and there is no string copying involved because all of this has already been done before the application has even started.
At the same time you can program in the same language as for the rest of the application, which makes for a very consistent development experience.
Integrated load balancing
[work-in-progress] The installation package contains a load balancer application that can almost seamlessly scale your vibe server installations as needed. But apart from load balancing, it has a feature which is maybe even more interesting.
When the sources for the application have been changed, the load balancer can automatically compile and run the modified application and seamlessly switch to it. The old application is shut down as soon as there are no more clients connected.
In the future, running an automated test suite before the new application instance is switched live will be supported. Rapid development processes with safe modifications on the live system become possible this way.