Skip to content

C API - no NULL pointer guarantee?

5 messages · Ivan Krylov, Erez Shomron, Simon Urbanek

#
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
1 day later
#
? Sat, 27 Jul 2024 14:36:20 +0300
"Erez Shomron" <r-mails at erezsh.org> ?????:
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.
#
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:
#
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
#
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: