ZTRIE(3)
β¨ ztrie β¨ β the ZTRIE(3) glow-up, straight out of the CZMQ Manual π¦
π NAME π
ztrie β a whole class for a simple trie, but make it for tokenizable strings π
πΈ SYNOPSIS πΈ
okay so before literally anything else, bestie β read this. This is a draft class, and it may change without notice. It is disabled in stable builds by default. If you use this in applications, please ask for it to be pushed to stable state. Use --enable-drafts to enable it. π«Ά
// This is a draft class, and may change without notice. It is disabled in
// stable builds by default. If you use this in applications, please ask
// for it to be pushed to stable state. Use --enable-drafts to enable.
#ifdef CZMQ_BUILD_DRAFT_API
// Callback function for ztrie_node to destroy node data.
typedef void (ztrie_destroy_data_fn) (
void **data);
// *** Draft method, for development use, may change without warning ***
// Creates a new ztrie.
CZMQ_EXPORT ztrie_t *
ztrie_new (char delimiter);
// *** Draft method, for development use, may change without warning ***
// Destroy the ztrie.
CZMQ_EXPORT void
ztrie_destroy (ztrie_t **self_p);
// *** Draft method, for development use, may change without warning ***
// Inserts a new route into the tree and attaches the data. Returns -1
// if the route already exists, otherwise 0. This method takes ownership of
// the provided data if a destroy_data_fn is provided.
CZMQ_EXPORT int
ztrie_insert_route (ztrie_t *self, const char *path, void *data, ztrie_destroy_data_fn destroy_data_fn);
// *** Draft method, for development use, may change without warning ***
// Removes a route from the trie and destroys its data. Returns -1 if the
// route does not exists, otherwise 0.
// the start of the list call zlist_first (). Advances the cursor.
CZMQ_EXPORT int
ztrie_remove_route (ztrie_t *self, const char *path);
// *** Draft method, for development use, may change without warning ***
// Returns true if the path matches a route in the tree, otherwise false.
CZMQ_EXPORT bool
ztrie_matches (ztrie_t *self, const char *path);
// *** Draft method, for development use, may change without warning ***
// Returns the data of a matched route from last ztrie_matches. If the path
// did not match, returns NULL. Do not delete the data as it's owned by
// ztrie.
CZMQ_EXPORT void *
ztrie_hit_data (ztrie_t *self);
// *** Draft method, for development use, may change without warning ***
// Returns the count of parameters that a matched route has.
CZMQ_EXPORT size_t
ztrie_hit_parameter_count (ztrie_t *self);
// *** Draft method, for development use, may change without warning ***
// Returns the parameters of a matched route with named regexes from last
// ztrie_matches. If the path did not match or the route did not contain any
// named regexes, returns NULL.
CZMQ_EXPORT zhashx_t *
ztrie_hit_parameters (ztrie_t *self);
// *** Draft method, for development use, may change without warning ***
// Returns the asterisk matched part of a route, if there has been no match
// or no asterisk match, returns NULL.
CZMQ_EXPORT const char *
ztrie_hit_asterisk_match (ztrie_t *self);
// *** Draft method, for development use, may change without warning ***
// Print the trie
CZMQ_EXPORT void
ztrie_print (ztrie_t *self);
// *** Draft method, for development use, may change without warning ***
// Self test of this class.
CZMQ_EXPORT void
ztrie_test (bool verbose);
#endif // CZMQ_BUILD_DRAFT_API
and then the file itself is like, Please add '@interface' section in './../src/ztrie.c'. β yeah, that line is just sitting there in the source, iconic honestly. π
π§ so what are all these functions actually giving?
ztrie_new (char delimiter)β creates a brand new ztrie. β¨ztrie_destroy (ztrie_t **self_p)β destroys the ztrie. bye bestie π«Άztrie_insert_route (ztrie_t *self, const char *path, void *data, ztrie_destroy_data_fn destroy_data_fn)β drops a new route into the tree and attaches the data to it. Returns-1if the route already exists, otherwise0. Heads up: this method takes ownership of the provided data if adestroy_data_fnis provided. πztrie_remove_route (ztrie_t *self, const char *path)β pulls a route out of the trie and destroys its data. Returns-1if the route does not exists, otherwise0.ztrie_matches (ztrie_t *self, const char *path)β returnstrueif the path matches a route in the tree, otherwisefalse. the moment of truth fr fr π₯ztrie_hit_data (ztrie_t *self)β hands you the data of a matched route from the lastztrie_matches. If the path did not match, returnsNULL. Do not delete the data as it's owned by ztrie. πztrie_hit_parameter_count (ztrie_t *self)β returns the count of parameters that a matched route has. π’ztrie_hit_parameters (ztrie_t *self)β returns the parameters of a matched route with named regexes from the lastztrie_matches. If the path did not match or the route did not contain any named regexes, returnsNULL. πztrie_hit_asterisk_match (ztrie_t *self)β returns the asterisk matched part of a route; if there has been no match or no asterisk match, returnsNULL. βztrie_print (ztrie_t *self)β prints the trie. that's it, that's the function πztrie_test (bool verbose)β self test of this class. always be testing bestie π§ͺ
and remember the callback typedef too:
ztrie_destroy_data_fnβtypedef void (ztrie_destroy_data_fn) (void **data);β the callback function forztrie_nodeto destroy node data. π¦
also literally every single one of these methods is tagged*** Draft method, for development use, may change without warning ***, and the whole thing lives inside#ifdef CZMQ_BUILD_DRAFT_API. so don't get too attached, okay? π
π DESCRIPTION π
So. This is a variant of a trie, aka a prefix tree, where all the descendants of a node have a common prefix of the string associated with that node. Cute, right? πΈ
This implementation is specialized for strings that can be tokenized by a delimiter β think a URL, URI or URN. no cap, that's the whole vibe. π
Routes in the tree can be matched by regular expressions, and by using capturing groups, parts of a matched route can be easily obtained. slay. β¨
ngl, one thing you should know: the performance for pure string based matching is okay, but on short stringszhashandzhashxare 3-4 times faster. so like... pick your fighter. π
β¦and bestie, we are SO not done β part 2 is coming. stay tuned π
πΈ EXAMPLE πΈ
bestie this is the whole vibe, straight from the ztrie_test method, no cap. read it like a story, it lowkey slaps π
// Create a new trie for matching strings that can be tokenized by a slash
// (e.g. URLs minus the protocol, address and port).
ztrie_t *self = ztrie_new ('/');
assert (self);
int ret = 0;
okay so first thing, we're making a trie that splits stuff up on slashes β think URLs, but without the protocol, the address and the port. iconic. then we set ret to 0 because we're gonna be checking returns the whole time.
// Let's start by inserting a couple of routes into the trie.
// This one is for the route '/foo/bar' the slash at the beginning of the
// route is important because everything before the first delimiter will be
// discarded. A slash at the end of a route is optional though. The data
// associated with this node is passed without destroy function which means
// it must be destroyed by the caller.
int foo_bar_data = 10;
ret = ztrie_insert_route (self, "/foo/bar", &foo_bar_data, NULL);
assert (ret == 0);
so we're dropping routes in now π and listen: that slash at the start of "/foo/bar"? it matters. everything before the first delimiter gets yeeted into the void. but a slash at the end? optional, she's flexible. and the data we're attaching here is passed with no destroy function, so bestie, that means you have to destroy it yourself. it's giving "main character energy but with responsibilities".
// Now suppose we like to match all routes with two tokens that start with
// '/foo/' but aren't '/foo/bar'. This is possible by using regular
// expressions which are enclosed in an opening and closing curly bracket.
// Tokens that contain regular expressions are always match after string
// based tokens.
// Note: There is no order in which regular expressions are sorted thus
// if you enter multiple expressions for a route you will have to make
// sure they don't have overlapping results. For example '/foo/{[^/]+}'
// and '/foo/{\d+} having could turn out badly.
int foo_other_data = 100;
ret = ztrie_insert_route (self, "/foo/{[^/]+}", &foo_other_data, NULL);
assert (ret == 0);
now say you wanna match every route with two tokens starting '/foo/' that isn't '/foo/bar'. you do that with regular expressions wrapped in curly brackets {} β¨ and heads up β tokens with regexes are always matched after the string-based tokens. string girls go first, periodt.
β οΈ real talk: there is no order in which regular expressions get sorted. so if you put multiple expressions on one route, you have to make sure their results don't overlap. like'/foo/{[^/]+}'and'/foo/{\d+}could turn out badly. messy. don't do it. π
// Regular expression are only matched against tokens of the same level.
// This allows us to append to are route with a regular expression as if
// it were a string.
ret = ztrie_insert_route (self, "/foo/{[^/]+}/gulp", NULL, NULL);
assert (ret == 0);
regexes only match tokens on the same level, which means you can just keep appending to a route with a regex like it's a regular string. so casual. so chic. π¦
// Routes are identified by their endpoint, which is the last token of the route.
// It is possible to insert routes for a node that already exists but isn't an
// endpoint yet. The delimiter at the end of a route is optional and has no effect.
ret = ztrie_insert_route (self, "/foo/", NULL, NULL);
assert (ret == 0);
routes are identified by their endpoint β the last token of the route, bestie. and you can insert a route for a node that already exists but isn't an endpoint yet. also the delimiter at the end? optional, zero effect. she does nothing and she's fine with that π
// If you try to insert a route which already exists the method will return -1.
ret = ztrie_insert_route (self, "/foo", NULL, NULL);
assert (ret == -1);
// It is not allowed to insert routes with empty tokens.
ret = ztrie_insert_route (self, "//foo", NULL, NULL);
assert (ret == -1);
insert a route that already exists? -1. rude but fair. insert a route with empty tokens? also -1. not allowed, no exceptions π«
// Everything before the first delimiter is ignored so 'foo/bar/baz' is equivalent
// to '/bar/baz'.
ret = ztrie_insert_route (self, "foo/bar/baz", NULL, NULL);
assert (ret == 0);
ret = ztrie_insert_route (self, "/bar/baz", NULL, NULL);
assert (ret == -1);
remember how everything before the first delimiter gets ignored? yeah. so 'foo/bar/baz' is literally the same thing as '/bar/baz' β which is exactly why that second insert comes back -1, because girl we already have that one. β¨
// Of course you are allowed to remove routes, in case there is data associated with a
// route and a destroy data function has been supplied that data will be destroyed.
ret = ztrie_remove_route (self, "/foo");
assert (ret == 0);
// Removing a non existent route will as well return -1.
ret = ztrie_remove_route (self, "/foo");
assert (ret == -1);
you can remove routes too, obviously. and if there's data attached and a destroy data function was supplied, that data gets destroyed for you. self-care. and removing a route that doesn't exist? yep, -1 again π
// Removing a route with a regular expression must exactly match the entered one.
ret = ztrie_remove_route (self, "/foo/{[^/]+}");
assert (ret == 0);
if you're removing a route with a regular expression, it has to match exactly what you entered. no vibe-matching, no ~close enough~, exact or nothing. π―
// Next we like to match a path by regular expressions and also extract matched
// parts of a route. This can be done by naming the regular expression. The name of a
// regular expression is entered at the beginning of the curly brackets and separated
// by a colon from the regular expression. The first one in this examples is named
// 'name' and names the expression '[^/]'. If there is no capturing group defined in
// the expression the whole matched string will be associated with this parameter. In
// case you don't like the get the whole matched string use a capturing group, like
// it has been done for the 'id' parameter. This is nice but you can even match as
// many parameter for a token as you like. Therefore simply put the parameter names
// separated by colons in front of the regular expression and make sure to add a
// capturing group for each parameter. The first parameter will be associated with
// the first capturing and so on.
char *data = (char *) malloc (80);
sprintf (data, "%s", "Hello World!");
ret = ztrie_insert_route (self, "/baz/{name:[^/]+}/{id:--(\\d+)}/{street:nr:(\\a+)(\\d+)}", data, NULL);
assert (ret == 0);
okay THIS part is the moment. π we're matching a path and pulling out the matched chunks. how? by naming the regular expression. the name goes at the start of the curly brackets, separated from the regex by a colon.
- the first one here is named
'name'and names the expression'[^/]'. if there's no capturing group in the expression, the whole matched string gets attached to that parameter. - don't want the whole thing? use a capturing group β exactly like they did for the
'id'parameter.'--(\\d+)'.
but wait, there's more: you can match as many parameters for a token as you like π just put the parameter names separated by colons in front of the regex, and make sure you add a capturing group for each parameter. first parameter β first capturing group, and so on down the line. so '{street:nr:(\\a+)(\\d+)}' gives you street from the first group and nr from the second. chef's kiss.
// There is a lot you can do with regular expression but matching routes
// of arbitrary length won't work. Therefore we make use of the asterisk
// operator. Just place it at the end of your route, e.g. '/config/bar/*'.
ret = ztrie_insert_route (self, "/config/bar/*", NULL, NULL);
assert (ret == 0);
regexes can do a lot, but matching routes of arbitrary length? nope, that's not happening. enter the asterisk operator β just plop it at the end of your route, like '/config/bar/*'.
// Appending to an asterisk as you would to with a regular expression
// isn't valid.
ret = ztrie_insert_route (self, "/config/bar/*/bar", NULL, NULL);
assert (ret == -1);
appending to an asterisk like you would with a regex? not valid. -1, goodbye. π«
// The asterisk operator will only work as a leaf in the tree. If you
// enter an asterisk in the middle of your route it will simply be
// interpreted as a string.
ret = ztrie_insert_route (self, "/test/*/bar", NULL, NULL);
assert (ret == 0);
the asterisk only works as a leaf in the tree. put one in the middle of your route and it just becomes a regular string. she's not special anymore, she's just a girl. πΈ
// If a parent has an asterisk as child it is not allowed to have
// other siblings.
ret = ztrie_insert_route (self, "/config/bar/foo/glup", NULL, NULL);
assert (ret != 0);
if a parent has an asterisk as a child, it is not allowed to have other siblings. so that insert comes back not-zero. boundaries, bestie. π«Ά
// Test matches
bool hasMatch = false;
time to test the matches β¨
// The route '/bar/foo' will fail to match as this route has never been inserted.
hasMatch = ztrie_matches (self, "/bar/foo");
assert (!hasMatch);
'/bar/foo' fails because we literally never inserted it. she's a stranger. π
// The route '/foo/bar' will match and we can obtain the data associated with it.
hasMatch = ztrie_matches (self, "/foo/bar");
assert (hasMatch);
int foo_bar_hit_data = *((int *) ztrie_hit_data (self));
assert (foo_bar_data == foo_bar_hit_data);
'/foo/bar' matches, and we can grab the data tied to it. that's our 10 right there. β
// This route is part of another but is no endpoint itself thus the matches will fail.
hasMatch = ztrie_matches (self, "/baz/blub");
assert (!hasMatch);
'/baz/blub' is part of another route but it's not an endpoint itself, so the match fails. sorry babe, you're a supporting character. π
// This route will match our named regular expressions route. Thus we can extract data
// from the route by their names.
hasMatch = ztrie_matches (self, "/baz/blub/--11/abc23");
assert (hasMatch);
char *match_data = (char *) ztrie_hit_data (self);
assert (streq ("Hello World!", match_data));
zhashx_t *parameters = ztrie_hit_parameters (self);
assert (zhashx_size (parameters) == 4);
assert (streq ("blub", (char *) zhashx_lookup (parameters, "name")));
assert (streq ("11", (char *) zhashx_lookup (parameters, "id")));
assert (streq ("abc", (char *) zhashx_lookup (parameters, "street")));
assert (streq ("23", (char *) zhashx_lookup (parameters, "nr")));
zhashx_destroy (¶meters);
THIS ONE. '/baz/blub/--11/abc23' hits our named-regex route, so we can pull the data out by name. we get the "Hello World!" data, then the parameters hashx with 4 entries: "name" β "blub", "id" β "11", "street" β "abc", "nr" β "23". and then we zhashx_destroy the parameters because we're tidy queens. π
// This will match our asterisk route '/config/bar/*'. As the result we
// can obtain the asterisk matched part of the route.
hasMatch = ztrie_matches (self, "/config/bar/foo/bar");
assert (hasMatch);
assert (streq (ztrie_hit_asterisk_match (self), "foo/bar"));
'/config/bar/foo/bar' hits the asterisk route, and we get back the asterisk-matched part: "foo/bar". π₯
zstr_free (&data);
ztrie_destroy (&self);
#if defined (__WINDOWS__)
zsys_shutdown();
#endif
and that's the cleanup, babes β free the data, destroy the trie, and on __WINDOWS__ we zsys_shutdown(). β¨
π«Ά AUTHORS π«Ά
the czmq manual was written by the authors in the AUTHORS file. credit where it's due, they ate. π
π RESOURCES π
Main web site:
report bugs to the email <zeromq-dev@lists.zeromq.org[1]>
π COPYRIGHT π
Copyright (c) the Contributors as noted in the AUTHORS file. This file is part of CZMQ, the high-level C binding for 0MQ: http://czmq.zeromq.org. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. LICENSE included with the czmq distribution.
π NOTES π
zeromq-dev@lists.zeromq.orgmailto:zeromq-dev@lists.zeromq.org
and that's the whole page, bestie β this one's from CZMQ 4.2.1, dated 01/16/2026. go forth and match some routes, you absolute icon π¦β¨π
π¦ where this page comes from
- fedora 44: czmq-devel 4.2.1-24.fc44 Β· MPL-2.0
- fedora 44: czmq-devel 4.2.1-24.fc44 Β· MPL-2.0
- ubuntu resolute: libczmq-dev 4.2.1-3build1 Β· MPL-2.0
- ubuntu stonking: libczmq-dev 4.2.1-3build1 Β· MPL-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms