habedi/zig-dbc
A design by contract library for Zig
Zig-DbC is a small library that provides a collection of functions to use design by contract (DbC) principles in software written in Zig programming language. It provides a simple and idiomatic API for defining preconditions, postconditions, and invariants that can be checked at runtime.
A common use case for DbC (and by extension Zig-DbC) is adding checks that guarantee the code behaves as intended. This can be especially useful, for example, during the implementation of complex data structures and algorithms (like balanced trees and graphs) where correctness depends on specific conditions being met.
preconditions
, postconditions
, and invariants
ReleaseFast
mode, all contract checks are removed at compile timeDebug
, ReleaseSafe
, and ReleaseSmall
modes to catch bugs earlycontract
function passes errors from your code to the callerIMPORTANT Zig-DbC is in early development, so bugs and breaking API changes are expected. Please use the issues page to report bugs or request features.
You can add Zig-DbC to your project and start using it by following the steps below.
Run the following command in the root directory of your project to download Zig-DbC:
zig fetch --save=dbc "https://github.com/habedi/zig-dbc/archive/<branch_or_tag>.tar.gz"
Replace <branch_or_tag>
with the desired branch or tag, like main
(for the development version) or v0.1.0
(for the latest release).
This command will download zig-dbc and add it to Zig's global cache and update your project's build.zig.zon
file.
Next, modify your build.zig
file to make zig-dbc available to your build target as a module.
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "your-zig-program",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 1. Get the dependency object from the builder
const zig_dbc_dep = b.dependency("dbc", .{});
// 2. Get Zig-DbC's top-level module
const zig_dbc_module = zig_dbc_dep.module("dbc");
// 3. Add the module to your executable so you can @import("zig-dbc")
exe.root_module.addImport("dbc", zig_dbc_module);
b.installArtifact(exe);
}
Finally, you can @import("dbc")
and start using it in your Zig application.
const dbc = @import("dbc");
pub fn MyStruct() type {
return struct {
const Self = @This();
field: i32,
is_ready: bool,
fn invariant(self: Self) void {
dbc.require(self.field > 0, "Field must always be positive");
}
pub fn doSomething(self: *Self) !void {
const old = .{ .field = self.field };
return dbc.contract(self, @TypeOf(old), old, struct {
fn run(ctx: @TypeOf(old), s: *Self) !void {
// Precondition
dbc.require(s.is_ready, "Struct not ready");
// ... method logic ...
s.field += 1;
// Postcondition
dbc.ensure(s.field > ctx.field, "Field must increase");
}
}.run);
}
};
}
You can find the API documentation for the latest release of Zig-DbC here.
Alternatively, you can use the make docs
command to generate the documentation for the current version of Zig-DbC
from the source code.
This will generate HTML documentation in the docs/api
directory, which you can serve locally with make serve-docs
and view in your web browser at http://localhost:8000.
Check out the examples directory for examples of how Zig-DbC can be used to build a variety of CLI applications.
See CONTRIBUTING.md for details on how to make a contribution.
Zig-DbC is licensed under the MIT License (see LICENSE).