Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve C API guidelines for return values #1129

Conversation

erlend-aasland
Copy link
Contributor

@erlend-aasland erlend-aasland commented Jun 26, 2023

Clarify that returning an error indicator should be done iff an
exception is raised.

For functions returning an int, a negative value should be returned.
Suggest -1 unless there is a need to distinguish between error types.


📚 Documentation preview 📚: https://cpython-devguide--1129.org.readthedocs.build/

Clarify that returning an error indicator should be done iff an
exception is raised.

For functions returning an int, a _negative value_ should be returned.
Suggest -1 unless there is a need to distinguish between error types.
@erlend-aasland
Copy link
Contributor Author

@encukou, are you ok with this change?

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

developer-workflow/c-api.rst Outdated Show resolved Hide resolved
developer-workflow/c-api.rst Outdated Show resolved Hide resolved
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
@erlend-aasland
Copy link
Contributor Author

Thanks, Victor and Adam.

@vstinner
Copy link
Member

Sadly, the newly added PyLong_AsInt() doesn't respect new guidelines, but it was a deliberate choice. I like the new PyDict_GetItemRef() API, it's pleasant to use it.

@encukou
Copy link
Member

encukou commented Aug 31, 2023

Well, I've thought about this, and realized I haven't seen an actual use case for distinguishing between error types. Is there one?
It seems that if an “error” is expected, like with “missing dict key”, that should be handled as another “success” (positive return value), so we don't need to allocate/init an exception object.
That leaves negative return values for “unexpected” errors, where the function can't really distinguish.

I'd love to see a counterexample :)

the newly added PyLong_AsInt() doesn't respect new guidelines

Well, it doesn't respect the old guidelines either, so that's unrelated to this PR :)

@vstinner
Copy link
Member

Well, I've thought about this, and realized I haven't seen an actual use case for distinguishing between error types. Is there one?

It's not common to report different error cases, but it happens, especially if the API does not raise an exception.

On error, Py_DecodeLocale() sets size to (size_t)-1 on memory allocation failure and (size_t)-2 on decoding error.

_Py_DecodeLocaleEx() has 3 error cases:

  • return -1: memory allocation failure
  • return -2: decoding error
  • return -3: unsupported error handler

For the caller, it's important to be able to distinguish the different errors. Extract of unicode_decode_locale():

    int res = _Py_DecodeLocaleEx(str, &wstr, &wlen, &reason,
                                 current_locale, errors);
    if (res != 0) {
        if (res == -2) {
            PyObject *exc;
            exc = PyObject_CallFunction(PyExc_UnicodeDecodeError, "sy#nns",
                                        "locale", str, len,
                                        (Py_ssize_t)wlen,
                                        (Py_ssize_t)(wlen + 1),
                                        reason);
            if (exc != NULL) {
                PyCodec_StrictErrors(exc);
                Py_DECREF(exc);
            }
        }
        else if (res == -3) {
            PyErr_SetString(PyExc_ValueError, "unsupported error handler");
        }
        else {
            PyErr_NoMemory();
        }
        return NULL;
    }

Co-authored-by: Petr Viktorin <encukou@gmail.com>
@erlend-aasland
Copy link
Contributor Author

Well, I've thought about this, and realized I haven't seen an actual use case for distinguishing between error types. Is there one?

I'd prefer if the guideline simply said "return -1 on error"; after all a guideline is meant for the general case, not the special case. For the extraordinary cases, it is ok to not follow the general guideline (after a discussion, of course).

@encukou
Copy link
Member

encukou commented Sep 4, 2023

It's not common to report different error cases, but it happens, especially if the API does not raise an exception.

Well, if an exception isn't raised, then the function should not return a negative value (or NULL) at all.
That's the main consistency win we're going for: you should be able to tell whether an exception was raised from the return value alone.


There are two parts of the question of “Does a negative value other than -1 signal that an exception was raised?”

  • what should functions return? (we can say -1 only for now, it's easy to allow more values later)
  • what should callers check for, == -1 or < 0? (on this we can't really change our minds later)

@erlend-aasland
Copy link
Contributor Author

erlend-aasland commented Sep 4, 2023

There are two parts of the question of “Does a negative value other than -1 signal that an exception was raised?”

  • what should functions return? (we can say -1 only for now, it's easy to allow more values later)
  • what should callers check for, == -1 or < 0? (on this we can't really change our minds later)

I would prefer callers to check for < 0 (any negative value implies an exception was raised). For C API implementers, I'd prefer simply to suggest using the return value is -1 (we're only talking about int returns here) if an exception is raised. If it makes it easier to formulate this, I'm fine with simply saying "return a negative value" instead of "return -1".

@encukou
Copy link
Member

encukou commented Sep 4, 2023

Yeah, return -1 and if (... < 0) is probably the best solution. It means all the negative return values except -1 probably can't be repurposed for anything useful in the future, but that's probably OK.

But perhaps this shouldn't be decided in an issue, especially since there'll be lots of API discussions at the sprint in about a month.

@erlend-aasland
Copy link
Contributor Author

But perhaps this shouldn't be decided in an issue, especially since there'll be lots of API discussions at the sprint in about a month.

Well, this PR has been up for two months already; there's no harm in waiting another month :)

@vstinner
Copy link
Member

vstinner commented Sep 4, 2023

Even when I'm 100% sure that a function can only return -1 on error, I prefer to write if (func() < 0). For me, it's a way to say: "if the function fails".

In the C library, it's unclear if a function returns 0 or 1 on success. It depends. But in the Python C API, it's likely that a function returns "a negative number" on error.

@erlend-aasland
Copy link
Contributor Author

Even when I'm 100% sure that a function can only return -1 on error, I prefer to write if (func() < 0). For me, it's a way to say: "if the function fails".

Yeah, that's the C idiom, so that would be my preferred style.

@vstinner
Copy link
Member

vstinner commented Sep 5, 2023

If a function can raise an exeeption, I don't see the point of having more than one negative value. The exception should contain the details: exception type and exception message.

In the worst case, if a very important additonal information cannot be passed in the exception, i would prefer to add a separated argument, than break the rule: return 1, 0 or -1.

@vstinner
Copy link
Member

vstinner commented Sep 5, 2023

My Py_DecodeLocale() example is unrelated: it doesn't raise an exception, and so uses a different API. I'm talking about public C API which raise exceptions.

Co-authored-by: Victor Stinner <vstinner@python.org>
@willingc willingc added the status-do not merge Do not merge the PR label Oct 10, 2023
@erlend-aasland
Copy link
Contributor Author

I'll leave this for the C API workgroup.

@erlend-aasland erlend-aasland deleted the c-api-guidelines/clarify-return-values branch October 11, 2023 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status-do not merge Do not merge the PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Document the preferred style for API functions with three, four or five-way returns
6 participants