6

A coworker has started advocating for a style for our user-facing APIs where every function takes a single struct parameter containing all the real parameters. That is, instead of:

void fn(int foo, bool bar);

we should do:

typedef struct {
    int foo;
    bool bar;
} fn_args_t;

void fn(fn_args_t args);

I've never encountered this style before. He argues that it makes it easy to add new parameters later without needing to update callers, although I worry that it also makes it easy to leave out an argument (either an existing one that was just overlooked, or one added later that you would want to pass non-zero for).

Is there a name for this style? Pros/cons? It seems like a bad sign that I've never seen it used before.

5
  • There are some structures in the Windows API that have evolved over the years. Some have distinct "Ex" function names and types, while others have a length member as the first member of the struct to distinguish versions. May 8, 2020 at 4:08
  • 4
    refactoring.guru/introduce-parameter-object is one reasoning... Also C does not have default arguments (stackoverflow.com/questions/1472138/c-default-arguments) which makes this approach useful in such cases too. May 8, 2020 at 4:08
  • 2
    @AlexeiLevenkov Interesting, but we shouldn’t take it out of context: The parameter object is advised when there is a repeating group of parameters. The repeating group is a clue that these parameters belong together and hence the struct. I don’t think that it’s a general advice to be use for every function.
    – Christophe
    May 8, 2020 at 12:18
  • In some narrow circumstances this technique may have specific advantages, but in general it will just be more wordy, and amounts to an aesthetic preference. In my view, a traditional argument list is best conceived of as an example of an anonymous struct, and making that structure explicit adds nothing in general.
    – Steve
    May 8, 2020 at 12:56
  • Many of the arguments for using structs only work when passing pointers to the struct instead of the struct itself. Which naturally has its own problems. May 8, 2020 at 14:39

2 Answers 2

8

In short

This approach makes sense in certain circumstances. But it should not be used as a systematic approach for every function.

In long with more details

The pros

It makes very much sense to group into a struct some groups of parameters that are used repetitively in several functions of your API. This approach is very natural: the repetitive use of the same parameters strongly suggests that these parameters are somehow related. And the struct only materializes this relation.

The pros is that it makes function calls shorter, makes the API easier to memorize, and in addition enlighten the user about the relation between the struct components.

There are a couple of examples in the standard library, such as asctime() and mktime() . But there are many more examples in well known API out there, where the struct is used together with other parameters (e.g. WinAPI where you often have the choice between using either x,y coordiantes or a POINT structure) and not as a lonesome struct parameter.

The cons

In everyday programming we sometimes forget the huge progress languages have made. Back in the early days of K&R it was very easy to make mistakes in parameter passing: when calling a function the parameters where just pushed onto the stack (or into registers) assuming that the function would use the same parameters in the same orders. When the caller made a mistake, for example used the parameters in the wrong order, or forgot one, the compiler didn't complain. I remember very well how nasty and difficult such errors were to find.

When Standard C later introduced the function prototypes and parameter checking, it was a blessing! A lot of dumb mistakes were immediately caught by the compiler. For me it was hundreds of hours of late night debugging avoided thanks to the compiler error message. And brought to a team, it's in the tousands of hours if not beyond.

The main problem with the struct, is exactly that: it makes call-argument-checking less effective: you pass the struct, the compiler is happy. You forgot to initialize one of the struct member? You will suffer; or worse, your code might create a security issue.

This is why this technique should use only when it brings a significant benefit that outweight the risk. Never use this as a systematic approach.

1

Very common technique. Easy to add features to the end of the struct which allow earlier versions to continue to work (they wouldn't expect the additional members and wouldn't access past what it believes to be the end of the struct).

A good idea is to make the first member a version number which also would indicate the size of the struct. Or just make the first member a byte length so the function knows which version of the struct it will be handling.

I've written many embedded system drivers and APIs like this. Very easy to add features without breaking other people's code.

5
  • Thank you for your contribution. But this seems to me more like a personal opinion based on personal experience, rather that a generalisable objective answer with pros&cons. In particular your first paragraph suggests that on contrary, you might miss to update older callers, causing newer versions to try to access struct members that were not initialize (or even worse that are out of bounds of the allocated object and thus UB)
    – Christophe
    May 8, 2020 at 12:23
  • That's really not at all what I said. If you don't like the technique, don't use it. But it is very common.
    – GaryLa
    May 8, 2020 at 21:40
  • 1
    No offense. And your advise about the version number is excellent. I just point ou the many risks associated with this technique, whether it’s common or not. But you made me curious: since you claim that it is very common, could you edit your answer to provide a reference to some well known API, library, or open source project, that uses this technique extensively, just to make your claim more verifiable?
    – Christophe
    May 9, 2020 at 7:14
  • I dig this personal experience stuff. I've also worked at various companies that used that style extensively in their C APIs, although with one tweak to accept the structure by pointer than by value. Well, if we have an extensible, open architecture with many third-party plugins calling our functions, it helps to leave breathing room to add to them in hindsight without breaking backward compatibility or versioning off a whole different interface and structure. This style allows for that...
    – user377672
    Oct 26, 2020 at 17:33
  • 1
    I had a co-worker one time who designed some interface with 3 or so functions. Then after we shipped he decided after some user reports that he needed another parameter. Problem is that he couldn't change the interface without breaking backward compatibility. So he built a whole new version of his interface, added like a hundred or two lines of code, with a complex adapter between the old and the new, and all of which could have been avoided if he just adopted this style. And we had to keep that convoluted code indefinitely of his v1 interface with v2 and all the adapter code in between.
    – user377672
    Oct 26, 2020 at 17:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.