conv.

All stories
TechQuiet 10d · day 13

matklad's 'don't let public functions call public functions' rule sparks debate

A short blog post laying out an API-design rule of thumb draws sustained discussion on Lobsters and Hacker News.

What to know

  • The post proposes a narrow, specific API-design rule: extract a shared private helper rather than letting one public function call another, to keep public interfaces from accumulating internal-use-only parameters.
  • The author carves out an explicit exception for 'shortcut' APIs, where a public function is deliberately a convenience chain of other public calls.
  • Discussion on Lobsters (66 points, 13 comments) outpaced the Hacker News submissions, and became a genuine engineering debate rather than simple link-sharing.
  • Commenters split between extending the rule's reasoning (interface stability, testing) and offering counterexamples (layered C/C++ APIs, Rust's Vec) where public-to-public calls are intentional.

The dispute Whether 'public function calling public function' is itself the problem, or merely a symptom of a broader, more situational issue about protecting a function's public contract — with layered-API advocates arguing the rule is too strict. · positions read across 24 posts and comments

many voices

The rule is sound, and the deeper issue is avoiding accidental damage to a function's public interface/contract.

  • “I feel like the better more general rule would be something like "do not damage the public interface unless absolutely necessary."”

    azimux · Lobsters ↗
some voices

The rule doesn't hold universally — layered or convenience public APIs legitimately have public functions calling other public functions.

  • “I’m not sure I agree with this. I tend to build public APIs in layers. The low layer public API exposes all necessary functionality.”

    david_chisnall · Lobsters ↗
some voices

The post's aside on testing ('mock your IO, not your methods') is unclear about where the I/O boundary should actually be drawn.

  • “Is mocking a MyCustomResourceStore "mocking your IO", or does "mock your IO" refer to some lower layer of the I/O stack? Where does I/O begin?”

    weberc2 · Lobsters ↗

matklad Blog post authorandrewrk Lobsters commenterazimux Lobsters commenterdavid_chisnall Lobsters commenter

How it unfolded 4 developments, newest first · click a bar or a number to jump postscomments

Peak 7 pieces in 3h at Nov 1, 7 AM; 29 pieces over 13 days (5 posts · 24 comments) Oct 29, 2 PM — 1 piece · 1 post — Hacker News 1Oct 29, 5 PM — quietOct 29, 8 PM — quietOct 29, 11 PM — quietOct 30, 2 AM — quietOct 30, 5 AM — 1 piece · 1 post — Hacker News 1Oct 30, 8 AM — quietOct 30, 11 AM — quietOct 30, 2 PM — quietOct 30, 5 PM — quietOct 30, 8 PM — quietOct 30, 11 PM — quietOct 31, 2 AM — quietOct 31, 5 AM — quietOct 31, 8 AM — quietOct 31, 11 AM — 3 pieces · 1 post · 2 comments — Lobsters 3Oct 31, 2 PM — 2 pieces · 2 comments — Lobsters 2Oct 31, 5 PM — 5 pieces · 1 post · 4 comments — Lobsters 4, Hacker News 1Oct 31, 8 PM — quietOct 31, 11 PM — quietNov 1, 1 AM — 1 piece · 1 comment — Lobsters 1Nov 1, 4 AM — 4 pieces · 1 post · 3 comments — Lobsters 3, Hacker News 1Nov 1, 7 AM — 7 pieces · 7 comments — Lobsters 7Nov 1, 10 AM — quietNov 1, 1 PM — 1 piece · 1 comment — Lobsters 1Nov 1, 4 PM — quietNov 1, 7 PM — quietNov 1, 10 PM — quietNov 2, 1 AM — 2 pieces · 2 comments — Lobsters 2Nov 2, 4 AM — quietNov 2, 7 AM — quietNov 2, 10 AM — quietNov 2, 1 PM — quietNov 2, 4 PM — quietNov 2, 7 PM — quietNov 2, 10 PM — quietNov 3, 1 AM — quietNov 3, 4 AM — quietNov 3, 7 AM — quietNov 3, 10 AM — quietNov 3, 1 PM — quietNov 3, 4 PM — 2 pieces · 2 comments — Lobsters 2Nov 3, 7 PM — quietNov 3, 10 PM — quietNov 4, 1 AM — quietNov 4, 4 AM — quietNov 4, 7 AM — quietNov 4, 10 AM — quietNov 4, 1 PM — quietNov 4, 4 PM — quietNov 4, 7 PM — quietNov 4, 10 PM — quietNov 5, 1 AM — quietNov 5, 4 AM — quietNov 5, 7 AM — quietNov 5, 10 AM — quietNov 5, 1 PM — quietNov 5, 4 PM — quietNov 5, 7 PM — quietNov 5, 10 PM — quietNov 6, 1 AM — quietNov 6, 4 AM — quietNov 6, 7 AM — quietNov 6, 10 AM — quietNov 6, 1 PM — quietNov 6, 4 PM — quietNov 6, 7 PM — quietNov 6, 10 PM — quietNov 7, 1 AM — quietNov 7, 4 AM — quietNov 7, 7 AM — quietNov 7, 10 AM — quietNov 7, 1 PM — quietNov 7, 4 PM — quietNov 7, 7 PM — quietNov 7, 10 PM — quietNov 8, 1 AM — quietNov 8, 4 AM — quietNov 8, 7 AM — quietNov 8, 10 AM — quietNov 8, 1 PM — quietNov 8, 4 PM — quietNov 8, 7 PM — quietNov 8, 10 PM — quietNov 9, 1 AM — quietNov 9, 4 AM — quietNov 9, 7 AM — quietNov 9, 10 AM — quietNov 9, 1 PM — quietNov 9, 4 PM — quietNov 9, 7 PM — quietNov 9, 10 PM — quietYesterday, 1 AM — quietYesterday, 4 AM — quietYesterday, 7 AM — quietYesterday, 10 AM — quietYesterday, 1 PM — quietYesterday, 4 PM — quietYesterday, 7 PM — quietYesterday, 10 PM — quietToday, 1 AM — quietToday, 4 AM — quietToday, 7 AM — quietToday, 10 AM — quietToday, 1 PM — quietToday, 4 PM — quietToday, 7 PM — quiet 1–23–4
Oct 30Oct 31Nov 1Nov 2Nov 3Nov 4Nov 5Nov 6Nov 7Nov 8Nov 9yesterdaynow · 9:50 PM ET
  1. 4

    matklad replies in the thread to clarify the testing argument

    Responding to a sub-thread questioning what 'mock your IO' means, matklad elaborates that there are two useful cuts in an application: the thin waist connecting the app to the external world, and a separate cut for testing the implementation of that waist itself.

    “That's a good observation, that there are many different ways to cut the interfaces such that pure business is on the one side, and side-effecting syscalls are on the other. And any cut works!”
    — matklad
    • I’m not sure I agree with this. I tend to build public APIs in layers. The low layer public API exposes all necessary functionality. The higher layers expose *convenient* functionality for common use cases. In C/C++, the higher-level interfaces are often header-only and designed to be inlined in the caller. They also serve as documentation: if you…

      david_chisnallapi,programming10d ago37▲view on Lobsters ↗
    2 more of the top 3 · 15 posts in this stretch
    • OP did say it’s OK to have helper functions that call _only_ other public functions.

      snejapi,programming10d ago14▲view on Lobsters ↗
    • Instead of reading my comment, you should watch Gary Bernhardt's superb talk "Boundaries", which discusses this very question. https://www.destroyallsoftware.com/talks/boundaries But I'll try to do a very brief answer to only your question. Your question sounds like it assumes a code structure that looks like this ... ``` def my_procedure()…

      Sietsebbapi,programming10d ago10▲view on Lobsters ↗
    all of them →
  2. 3

    Several commenters push back that the rule is not universal

    david_chisnall describes building layered public APIs in C/C++ where higher-level convenience functions intentionally call lower-level public functions, and ohrv notes Rust's Vec has many public methods calling other public methods for safety and reuse reasons.

    “I’m not sure I agree with this. I tend to build public APIs in layers.”
    — david_chisnall
    • Or you could do whatever makes the most sense at a given moment and then when the circumstances change, refactor your code so it keeps making the most sense under the new circumstances

      enobayramapi,programming10d ago3▲view on Lobsters ↗
  3. 2

    Commenters propose alternative framings of the rule

    Lobsters commenters offer concrete experiences and reframings: andrewrk describes a stack-trace use case that depends on the pattern, azimux argues the deeper issue is damaging a function's public interface rather than the call itself, and telemachus questions whether the example should use two distinct private helpers instead of one.

    “I feel like the better more general rule would be something like "do not damage the public interface unless absolutely necessary."”
    — azimux
    • The problem I see there is not that foo calls bar specifically, but rather that bar's public interface has been damaged as an implementation detail of foo as opposed to a tiny refactor. So I feel like the better more general rule would be something like "do not damage the public interface unless absolutely necessary." Though, if using the avoid…

      azimuxapi,programming11d ago23▲view on Lobsters ↗
    2 more of the top 3 · 8 posts in this stretch
    • A concrete thing related to this I just ran into yesterday, is that when reporting certain kinds of errors, I collect a stack trace for later rendering to indicate which public function call was responsible for the creation of a particular artifact. You can make the first stack frame be the callsite of the public function by capturing the return…

      andrewrkapi,programming11d ago22▲view on Lobsters ↗
    • I've been in the shoes of a developer adding optional function after optional function to make things "easier" for users. Some of the worst APIs grow this way. "Everything is in one function call" except that you've effectively reinvented having multiple functions by way of parameters. Also, "sanctified ink" is definitely a phrase I will be using…

      bputhapi,programming11d ago8▲view on Lobsters ↗
    all of them →
  4. 1

    Lobsters submission becomes the main discussion hub

    The post is submitted to Lobsters by telemachus and gains 66 points and 13 comments, far outpacing the earlier and parallel Hacker News submissions, and becomes the primary venue for debate.

    “By proactively making the shared implementation private, the tension is defused.”
    — matklad, blog post author · source
  5. background

    matklad publishes 'Avoid Public Functions Calling Other Public Functions' — The blog post proposes a rule of thumb: when a public function foo needs logic already in public function bar, extract a private helper for both to call, rather than having foo call bar directly, to keep the public API free of parameters added only for internal reuse.

What people are saying 16 voices from 1 site · best of 24 · verbatim

Still unanswered
  • Where exactly should the I/O boundary be drawn when deciding what to mock in tests?
  • Would splitting the shared logic into multiple distinct private helpers (rather than one parameterized helper) better preserve the spirit of the rule?