Hello,
I'm working on bindings for the API (for zig), and was wondering if the R's C API guarantees it won't return null pointers?
The only reference I found in the "Writing R Extensions" manual where this not the case is `R_tryEval` and `R_tryEvalSilent`.
Otherwise it's unclear.
The reason I care about this is syntax. Because I don't know whether SEXPs are NULL or not, then I wrap the SEXP in an optional type, and the burden is on the user to either check or assert every time you want to handle an optional SEXP.
It's not too bad and provides null safety, but can get excessive.
See an example from one of my test functions (question mark unwraps the optional. asserting it is not null):
```
export fn testAsScalarVector() Robject {
const results = rzig.vec.allocVector(.List, 23).?.protect();
defer rzig.gc.protect_stack.unprotectAll();
results.?.setListObj(0, rzig.vec.asScalarVector(@as(f32, 1.32456e+32)));
results.?.setListObj(1, rzig.vec.asScalarVector(@as(f32, -9.87123e-32)));
results.?.setListObj(2, rzig.vec.asScalarVector(math.inf(f32)));
results.?.setListObj(3, rzig.vec.asScalarVector(-math.inf(f32)));
results.?.setListObj(4, rzig.vec.asScalarVector(math.nan(f32)));
results.?.setListObj(5, rzig.vec.asScalarVector(@as(f64, -9.1e+300)));
results.?.setListObj(6, rzig.vec.asScalarVector(@as(f64, 1.2e-300)));
results.?.setListObj(7, rzig.vec.asScalarVector(math.inf(f64)));
results.?.setListObj(8, rzig.vec.asScalarVector(-math.inf(f64)));
results.?.setListObj(9, rzig.vec.asScalarVector(math.nan(f64)));
results.?.setListObj(10, rzig.vec.asScalarVector(-9.1e+307));
results.?.setListObj(11, rzig.vec.asScalarVector(1.2e-307));
results.?.setListObj(12, rzig.vec.asScalarVector(1.0e+500)); // Inf
results.?.setListObj(13, rzig.vec.asScalarVector(-1.0e+500)); // -Inf
results.?.setListObj(14, rzig.vec.asScalarVector(5));
results.?.setListObj(15, rzig.vec.asScalarVector(-5));
results.?.setListObj(16, rzig.vec.asScalarVector(@as(u32, 4)));
results.?.setListObj(17, rzig.vec.asScalarVector(@as(i32, -4)));
results.?.setListObj(18, rzig.vec.asScalarVector(@as(u0, 0)));
results.?.setListObj(19, rzig.vec.asScalarVector(@as(u150, 2_000_000_000)));
results.?.setListObj(20, rzig.vec.asScalarVector(@as(i150, -2_000_000_000)));
results.?.setListObj(21, rzig.vec.asScalarVector(true));
results.?.setListObj(22, rzig.vec.asScalarVector(false));
return results;
}
```
It would be nice to be able to drop the question mark, but only if R guarantees null safety at the API level. Then I would declare optional return types only for documented cases like `R_tryEval`
Appreciate your time an help!
Thanks,
- Erez
C API - no NULL pointer guarantee?
5 messages · Ivan Krylov, Erez Shomron, Simon Urbanek
1 day later
? Sat, 27 Jul 2024 14:36:20 +0300 "Erez Shomron" <r-mails at erezsh.org> ?????:
I'm working on bindings for the API (for zig), and was wondering if the R's C API guarantees it won't return null pointers? The only reference I found in the "Writing R Extensions" manual where this not the case is `R_tryEval` and `R_tryEvalSilent`.
Based on what I've been reading while working on (still very much incomplete) <https://aitap.codeberg.page/R-api>, I think that these are the only two cases where a SEXP can be null, precisely because a null pointer is distinguished from every possible value that could be returned by eval(). Some APIs may accept a null SEXP (with comments in the source code that this is to support some legacy code, not the intended use), but when R wants to return a value, it is always allocated through the memory manager. A failing allocation will longjmp() away from the frame, not return a null pointer. It's hard to prove a negative, though.
Best regards, Ivan
Thank you Ivan, At this point, without it being documented explicitly, I tend to lean on the safe side. If the non-null assumption is ever incorrect, on debug and safe builds unwrapping is an assert that will guarantee to crash R. While the source code has plenty of NULL checks, also for some SEXP, it's hard to tell just from grepping if any are related to the public API or not. Secondly as zig's documentation indicates: "Edge cases matter". If I'm making bindings in this language, I should care about edge cases as well. Last but not least, thanks for sharing your WIP documentation. If you agree, I definitely can use that as a reference as I progress. Best regards, - Erez
On Mon, Jul 29, 2024, at 1:23 PM, Ivan Krylov wrote:
? Sat, 27 Jul 2024 14:36:20 +0300 "Erez Shomron" <r-mails at erezsh.org> ?????:
I'm working on bindings for the API (for zig), and was wondering if the R's C API guarantees it won't return null pointers? The only reference I found in the "Writing R Extensions" manual where this not the case is `R_tryEval` and `R_tryEvalSilent`.
Based on what I've been reading while working on (still very much incomplete) <https://aitap.codeberg.page/R-api>, I think that these are the only two cases where a SEXP can be null, precisely because a null pointer is distinguished from every possible value that could be returned by eval(). Some APIs may accept a null SEXP (with comments in the source code that this is to support some legacy code, not the intended use), but when R wants to return a value, it is always allocated through the memory manager. A failing allocation will longjmp() away from the frame, not return a null pointer. It's hard to prove a negative, though. -- Best regards, Ivan
Erez, I think the API is very explicit about this, NULL is not an accepted input for any function taking SEXP by design. The special case of try*Eval() return values can be taken as a case where the resulting object is not actually SEXP but rather a special type which can be NULL (=failure) or SEXP. It may be even perhaps useful to declare it as a separate type to make this clearer, but I don't think it was really necessary so far. Any other cases where a SEXP value somehow escapes as NULL should be considered a bug. Cheers, Simon
On 30/07/2024, at 2:54 PM, Erez Shomron <r-mails at erezsh.org> wrote: Thank you Ivan, At this point, without it being documented explicitly, I tend to lean on the safe side. If the non-null assumption is ever incorrect, on debug and safe builds unwrapping is an assert that will guarantee to crash R. While the source code has plenty of NULL checks, also for some SEXP, it's hard to tell just from grepping if any are related to the public API or not. Secondly as zig's documentation indicates: "Edge cases matter". If I'm making bindings in this language, I should care about edge cases as well. Last but not least, thanks for sharing your WIP documentation. If you agree, I definitely can use that as a reference as I progress. Best regards, - Erez On Mon, Jul 29, 2024, at 1:23 PM, Ivan Krylov wrote:
? Sat, 27 Jul 2024 14:36:20 +0300 "Erez Shomron" <r-mails at erezsh.org> ?????:
I'm working on bindings for the API (for zig), and was wondering if the R's C API guarantees it won't return null pointers? The only reference I found in the "Writing R Extensions" manual where this not the case is `R_tryEval` and `R_tryEvalSilent`.
Based on what I've been reading while working on (still very much incomplete) <https://aitap.codeberg.page/R-api>, I think that these are the only two cases where a SEXP can be null, precisely because a null pointer is distinguished from every possible value that could be returned by eval(). Some APIs may accept a null SEXP (with comments in the source code that this is to support some legacy code, not the intended use), but when R wants to return a value, it is always allocated through the memory manager. A failing allocation will longjmp() away from the frame, not return a null pointer. It's hard to prove a negative, though. -- Best regards, Ivan
______________________________________________ R-help at r-project.org mailing list -- To UNSUBSCRIBE and more, see https://stat.ethz.ch/mailman/listinfo/r-help PLEASE do read the posting guide http://www.R-project.org/posting-guide.html and provide commented, minimal, self-contained, reproducible code.
Thank you and I appreciate the explanation. I will drop the optional from all but the tryEval() cases. Thanks, - Erez
On Tue, Jul 30, 2024, at 8:32 AM, Simon Urbanek wrote:
Erez, I think the API is very explicit about this, NULL is not an accepted input for any function taking SEXP by design. The special case of try*Eval() return values can be taken as a case where the resulting object is not actually SEXP but rather a special type which can be NULL (=failure) or SEXP. It may be even perhaps useful to declare it as a separate type to make this clearer, but I don't think it was really necessary so far. Any other cases where a SEXP value somehow escapes as NULL should be considered a bug. Cheers, Simon
On 30/07/2024, at 2:54 PM, Erez Shomron <r-mails at erezsh.org> wrote: Thank you Ivan, At this point, without it being documented explicitly, I tend to lean on the safe side. If the non-null assumption is ever incorrect, on debug and safe builds unwrapping is an assert that will guarantee to crash R. While the source code has plenty of NULL checks, also for some SEXP, it's hard to tell just from grepping if any are related to the public API or not. Secondly as zig's documentation indicates: "Edge cases matter". If I'm making bindings in this language, I should care about edge cases as well. Last but not least, thanks for sharing your WIP documentation. If you agree, I definitely can use that as a reference as I progress. Best regards, - Erez On Mon, Jul 29, 2024, at 1:23 PM, Ivan Krylov wrote:
? Sat, 27 Jul 2024 14:36:20 +0300 "Erez Shomron" <r-mails at erezsh.org> ?????:
I'm working on bindings for the API (for zig), and was wondering if the R's C API guarantees it won't return null pointers? The only reference I found in the "Writing R Extensions" manual where this not the case is `R_tryEval` and `R_tryEvalSilent`.
Based on what I've been reading while working on (still very much incomplete) <https://aitap.codeberg.page/R-api>, I think that these are the only two cases where a SEXP can be null, precisely because a null pointer is distinguished from every possible value that could be returned by eval(). Some APIs may accept a null SEXP (with comments in the source code that this is to support some legacy code, not the intended use), but when R wants to return a value, it is always allocated through the memory manager. A failing allocation will longjmp() away from the frame, not return a null pointer. It's hard to prove a negative, though. -- Best regards, Ivan
______________________________________________ R-help at r-project.org mailing list -- To UNSUBSCRIBE and more, see https://stat.ethz.ch/mailman/listinfo/r-help PLEASE do read the posting guide http://www.R-project.org/posting-guide.html and provide commented, minimal, self-contained, reproducible code.