Could you invert the relationship instead? (I can’t answer it for you, but something to think about) (or rather un-invert the inversion?)
Instead of creating your code with a hole that gets plugged by the user and becomes highly generic and abstract.
Could you instead let the user just reach into your data structure and access data directly?
So instead of your code driving the control flow and calling back to the user through provided callbacks, let the user write the control flow and then use documentation to tell the user: call this, before that.
I don’t know if my point comes across very well, I find it a bit difficult to describe in abstract terms.
Basically what I am thinking of is to emulate something like the raylib api, where you call raylib functions in specific ways like this:
pub fn main() !void
{
ray.InitWindow(800, 450, "raylib [core] example - basic window");
while (!ray.WindowShouldClose())
{
// Update
//----------------------------------------------------------------------------------
// TODO: Update your variables here
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
ray.BeginDrawing();
ray.ClearBackground(ray.WHITE);
ray.DrawText("Congrats! You created your first window!", 190, 200, 20, ray.LIGHTGRAY);
ray.EndDrawing();
//----------------------------------------------------------------------------------
}
ray.CloseWindow();
}
Instead of doing something like this:
pub fn main() !void {
var window = Window(struct {
pub const width = 800;
pub const height = 450;
pub const title = "raylib [core] example - basic window";
pub fn update() !void {
}
pub fn draw() !void {
ray.ClearBackground(ray.WHITE);
ray.DrawText("Congrats! You created your first window!", 190, 200, 20, ray.LIGHTGRAY);
}
});
try window.run();
}
While the latter code may look prettier (at first glance) it hides internal plumbing and is annoying, because it forces you to put your code into a bunch of predefined callback slots and also forces you into creating context for communicating between these different slots, it forces an arbitrary structure on the user.
I appreciate the former because it doesn’t force specific structure on the user and just lets the user build their program how they want to, allowing the user to use the stack more naturally, instead of having to put things into context objects (as a poor replacement for the stack).
So my question is:
Could you get rid of the callback and instead design your library in such a way that it doesn’t take away the control from the user, but instead is being controlled by the user of the library.
I also think that doing that may lead to a design, where the user can directly deal with errors, right where they happen, instead of those errors being obscured by inversion of control and the more abstract code it forces.
The user can still choose to create abstract interfaces on top of it, but they are no longer forced into it.