Hacker News new | ask | show | jobs
by kabdib 5096 days ago
... annnnd, we've reinvented callbacks. They've been around a long time, they're practically primordial. Cavemen were knapping callbacks out of flint in anticipation of the first computers.

I have some rules for these that have served me well:

1. Never hold locks when you're calling someone back. You have NO idea how the caller is going to abuse you.

2. Be prepared to handle recursion (usually with deferral of some kind, or possibly an error), because at some point the callee is going to call you back.

3. Always provide a 'void*' or some other context to be passed along with the function pointer (or the callee is doomed to use a global).

4. Document what the callee can do. For instance, if you're a timer object making "alarm clock" callbacks, forbid callees from taking too much time before they return; assert if they blow it.

1 comments

Well, not reinventing to be honest, rather redocumenting. There's a lot of cavemen behavior that still needs to be taught to "cave-kids". You're correct, an experienced C programmer should know this, but I do expect more to join the ranks. Good list of rules! Well, actually good to keep in mind even with the original tangled design but when resolving the tangle it actually clarifies the responsibility of this even further (the documentation, making sure there's a void* etc etc).
Agreed. I find articles like this great for passing along to junior engineers to help extend their vocabulary, making our pairing more productive. So, thank you for this nice little example of using dependency injection to remove static dependencies. Which, besides reducing "smell", makes unit testing more tractable.

I do agree with the parent's list--especially the "void*" pointer for passing around context. Unless the injected routine is doing something very simple, some context is almost always required. Providing that along with the function pointer helps avoid globals--and thus avoid unnecessary singletons.

I could see how providing a complete example that illustrates the use of this context might muddy the core focus of your article. Maybe a follow-up article? :) Thanks again for creating teaching material for me.

I'll add: The times that I've left off a void* context, I've always wanted one later. Just put one there. Honest. Don't think about functions without also thinking about their environments.

(In languages with closures, you'd just use a closure. Passing a void* around is C's meatball way of expressing an execution context).