Yeah, too magic. Close APIs are pretty standard, and the value will be ignored by the user afterwards.
We’re talking about different things because of a disagreement, but not talking about the disagreement itself. I don’t disagree with anything that you’re saying, but it’s like you keep changing the subject.
There’s no need or benefit to using an output parameter here at all. If the returned type is zero/false/NULL (all the same in C), then it failed, otherwise the return value is the reference. Just like how we do everywhere else. The advantage of this is we implicitly get defined behaviour when a user ignores the return value:
// With output parameter
PyInterpreterRef ref;
PyInterpreterRef_Get(&ref); // ignored return value - `ref` is uninitialized/undefined
PyInterpreterRef_Close(ref); // no defined value of `ref` here, so we assume it's valid and then crash
// With return value
PyInterpreterRef ref = PyInterpreterRef_Get();
// didn't check `if (!ref) goto error;`
PyInterpreterRef_Close(ref); // `ref` is NULL, so we can ignore it/fail appropriately
Obviously we can make the first one safe by defining NULL/0 as a defined (but invalid) value, and then assigning it in the error case (without documenting that it’s part of the API, as you suggested).
Or we can just make it the error case and then everything else works naturally. Simpler for all.
To expand on my preference for the return value: the deciding factor as to which approach is better is how frequently will the reference be allocated and immediately assigned? If that’s the common case (as in my examples above), then initializing followed by a test (my second example) is better. If it’s more common to allocate the reference separately from where it’s initialized (e.g. in a malloc’d struct), and especially if getting its value is likely to be part of a chain of operations where boolean results can be combined and short circuiting used, then having a boolean result is likely to be either equivalent or better.
In this case, it seems most likely that interpreter references will be initialized at definition time, and failure likely means that Python exceptions aren’t available at all while the subsequent operation is probably going to produce a Python exception, hence chaining operations is unlikely. So I think we should go with the nullable PyInterpreterRef return value rather than output arguments.