You use callbacks to iterate through containers. If your data structure allows this, you can try writing crawl code using iterators, which allows you to write what is now a separate callback as the body of the loop.
For example, if you have a binary tree, a recursive traversal with a callback looks something like this:
typedef struct node_t node_t; struct node_t { const char *id; node_t *left, *right; }; void traverse(const node_t *node, void (*func)(const node_t *n)) { if (node) { traverse(node->left, func); func(node); traverse(node->right, func); } }
And it is used like this:
traverse(head, lambda(void, (const node_t *n){ puts(n->id); }));
As you already noted, in the C standard, a function must be a global function with a restriction that you cannot easily and safely print with data that is not stored in the node itself.
For a standard and more intuitive way to navigate the tree, you can rewrite the traversal as an iterative code and save the state in an iterator:
typedef struct node_iter_t node_iter_t; struct node_iter_t { node_t *next; node_t *node; node_t *stack[32]; int nstack; }; int next_node(node_iter_t *it) { it->node = it->next; while (it->nstack || it->node) { while (it->node) { it->stack[it->nstack++] = it->node; it->node = it->node->left; } it->node = it->stack[--it->nstack]; it->next = it->node->right; return 1; } return 0; }
Iterator code is more verbose than recursive traversal, but client code is a simple loop that can access other local variables in a function:
node_iter_t it = {head}; int i = 0; while (next_node(&it)) { printf("%d: %s\n", i++, it.node->id); }
Of course, your container may not be suitable for such rewriting.