I’d prefer to see an activate/deactivate API rather than ensure/clear:
PyThreadState *ts
int err = PyThreadState_New(interp, &ts);
if (err) {
const char *msg;
PyThreadState_GetErrorMessage(err, msg);
print(msg);
exit;
}
int err = PyThreadState_Activate(ts);
if (!err) {
// do Python stuff
PyThreadState_Deactivate(ts);
} else { ... }
// when done
PyThreadState_Delete(ts);
(Details open to change - it’s an illustration, not a “yes/no” spec.)
For CPython in its current state, we’d need to fail if you tried to activate a thread state on a different OS thread, because they have strong affinity. But there’s no reason that couldn’t change in the future.
More importantly though, I think this makes it clearer who owns the thread state - a manually created one is controlled by the code that created it, and once it’s deleted it can’t be activated again.
Internally, we can set them up to be deleted when our bootstrap function returns, and can warn/fail if the interpreter state is being closed with threads still active.
I also much prefer just using error codes (they can all be negative) and having a function to get the string over returning structs. It’s basically just making an internal API into a public one (I can’t imagine we wouldn’t have a “get error message” helper function), but I see no harm in letting users choose when they get the static string. We don’t have to force it into their hands.