How to design API function that creates Something

I’ve recently become totally obsessed with API design, I think in part because I’m about to embark on some projects that I really want to nail the API for developers on, and partly because part of my work life is implementing GPGPU compute APIs for clients of my company Codeplay.

For background viewing, Casey Muratori has an awesome talk he gave back in 2004, about his time designs APIs at RAD. You should check out his talk ‘Designing and Evaluating Reusable Components‘. Also, Matt Gemmell wrote a great blog post about his time designs reusable components for iOS and Mac OSX. You should check out ‘API Design‘.

For this post, I’m concentrating on one of my primary unsolved annoyances in API design – how do you create something. It is such a core part of any API (create my widget X, physics hypercube Y, or even compiler Z) to be able to create things that the API has encapsulated for you, and then use them. The problem is – what if the creation goes wrong? How does the API tell you when something bad happened? Any suggestions that exceptions should be used for these examples will cause the suggester to suffer a similar fate as Old Yeller!

From my experience, there are 5 different ways in C to have an API that can create something, and also be able to signal an error occurred.

For each of these APIs I’m taking a bool and an int parameter as fluff parameters to signify some sort of user control over the creation, and we are creating an opaque type ‘Something’.

API A

int APICreateSomethingA(bool param1, int param2, Something * something);

For this API, we are returning an errorCode via the return, and also returning a Something via a pointer to a Something being passed in as a parameter. This is similar to how OpenCL 1.2 creates cl_event’s in any of the clEnqueue* functions (like clEnqueueBarrierWithWaitList for instance).

The problems with this API design;

  • we just return an int, there is no verbosity on the parameter to say what is being returned. This means that we require the header to have documentation saying that the return type is actually representing an error code, and all documentation related to this function to also contain what the value represents.
  • what happens if a user passes in a NULL pointer for something? What is the expected behaviour?

API B

Something APICreateSomethingB(bool param1, int param2, int * errorCode);

For this API, we are returning the Something via the return, and also returning the (now much more obvious) error code via the input parameter.

The problems with this API design;

  • what happens if a user passes in a NULL pointer for errorCode? Does this mean they don’t want the errorCode value? Don’t care about it? Or was it a mistake? If they didn’t pass in a valid pointer for errorCode, what do we do if something goes wrong?

API C

Something APICreateSomethingC(bool param1, int param2)

For this API, we are returning the Something via the return, and don’t care to return an errorCode at all. APIs like this usually make the assumption that if Something is NULL there was an error, or we have a followup method akin to;

int APIGetSomethingErrorCode(Something something);

I detest this approach the most in all honesty – I’m including it merely because I have witnessed such atrocities in the past!

API D

Many modern languages (for instance both Swift and Python) allow multiple things to be returned from a function.

Swift has syntax like;

func minMax(array: [Int]) -> (min: Int, max: Int) {
  // ...
}

Now (fortunately or unfortunately depending on your slicing of the pie) C doesn’t have a similar mechanism. To get a similar approach, we use a little helper struct that contains both the error code and the Something.

typedef struct _APICreateSomethingResult
{
  int errorCode;
  Something something;
} APICreateSomethingResult;

APICreateSomethingResult APICreateSomethingD(bool param1, int param2);

This approach is a really simple and nice way to return two things from the API – it encapsulates them all within a single return type. I remember being taught at University that these sort of shenanigans were evil incarnate – although I suspect that the days when returning 64->128 bytes via a function call was bad are long passed! We should still take care not to return excessively large Something’s via this method though as it will result in the compiler having to do much more fluffing with the data that is unwarranted and unnecessary.

One of the downsides though of doing this in C/C++ is we can’t discard the values we don’t want. One of things about Python I especially love is that you can return 5/6 things and only use say 1/2 of them, meaning that in the language itself you are throwing away stuff you didn’t want and or need. If we were able to do this in C/C++ we might be able to optimise the code a tad more.

API E

LLVM/Clang uses an templated approach something like;

template<T> struct ErrorOr;

The idea being that you either get an error, or you get the T you wanted. There is some footery that we can do with this if we can assume that the Something is a pointer, and it is at least 2 byte aligned.

typedef struct _APIErrorOrSomething
{
  union
  {
    intptr_t errorCode;
    Something something;
  };
} APIErrorOrSomething;

APIErrorOrSomething APICreateSomethingE(bool param1, int param2);

Then when we consume this APIErrorOrSomething struct, we do something like;

APIErrorOrSomething result = APICreateSomethingE(true, 42);
bool haveError = result.errorCode & 1;
result.errorCode &= ~1;
if(haveError)
{
  // guess we had an error?
  int errorCode = result.errorCode;
}
else
{
  Something something = result.something;
}

All of this code is rather disgusting when you view it in its raw form, it is much ‘nicer’ when it is hidden under C++ accessor methods. But either way, it saves a little bit of memory in the return type, but has the requirement that we either have a Something or an error code – we can’t have both.

Summary

I have came to no conclusions, nor have I myself decided which of these methods I actually like the most in all honesty! Feedback would be appreciated (and perhaps there are yet more API variants for this creation API that I haven’t covered!). I fired all the code together on GitHub – TestAPIDesigns.

Advertisements

13 thoughts on “How to design API function that creates Something

    1. You did see my comment about old yeller right? 🙂

      But in general I don’t like exceptions, I’ve never compiled my C/C++ code with exceptions on ever (I’m a no exceptions no RTTI kinda guy), but if I was to allow exceptions in an API your approach is totally valid.

      Like

  1. When I design an API, I usually write the “ideal programme using it” before. All approaches can be valid depending on how you are going to use the API. Also what can the error be? If you write a black box of some sort (like a physic engine) which won’t access the network or anything, the only error you’d encounter is malloc() returning null, for such API, you may just return NULL and use API C. But if you have many possible errors (network, file not found…) I’d go with API B, but I’d use a struct pointer for the error instead of an int (to be able to return a complex error object).

    Like

    1. That is an ok approach – but you do want to let power users have as much control as they want. I want them to be able to allocate for me, see my state, maybe even provide the worker thread for any processing that I would have done asynchronously, etc. It is a hard balance between allowing easy setup and progress, followed up by control and understanding when looking for performance.

      Opaque versus known struct types is another blog post in itself I think I might write though!

      Like

  2. The last two APIs are basically just implementations of the Either monad in Haskell. I suggest that this is the best approach. Essentially, your question boils down to the fact that the C type system does not adequately express what’s happening inside your function and what should be done about it. The C++ type system, using templates, can give an adequate description.

    There are two reasons why the template approach is useful. First, the ErrorOr construct can be reused on things of different types. Secondly, You can safely combine functions which take EitherOr and return EitherOr until you get to a point where you are done processing and want to know the result.

    Like

    1. And Either is just another way of expressing an exception. I.E. the semantics of Either (when treated as a Monad) are identical to exceptions all the way up to and including try/catch/finally. So, no matter how you slice it, exceptions are the best way to model errors. Now, the author seems to take exception (heh) to the way exceptions are implemented in C++, which honestly I can’t blame him for since I happen to hate the way virtually everything in C++ is implemented. Using ErrorOr to re-implement exceptions in C++ (or C) as an alternative to the built in implementation might be a valid approach, but you should be honest with yourself and acknowledge that that’s what you’re doing.

      Like

  3. I fail to see the issue with API A — just typedef an enum to somethingErrorCode_t and describe what codes are returned when (e.g. returns InvalidParameter when something is NULL). Or do users of APIs not read documentation anymore?

    Like

    1. Problem is that you want it to be totally usable. Like if I just have auto-complete on say Sublime Text or Visual Studio or even QTCreator, I might just have the function signature and nothing else. Making that as readable and understandable as possible should always be the aim of the API developer in my opinion!

      Like

  4. I second the notion that one should use exceptions to handle exceptions. I realize C doesn’t support that, so you are left with either return codes, checking last error, or pointers. In general, I like a functional programming approach. Returning a value from the function is more important than exception handling.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s