[Rd] some questions about R internal SEXP types

Dan Kortschak
Mon, 07 Sep 2020 23:31:54 -0000

Thanks, Gabriel.

On Mon, 2020-09-07 at 14:38 -0700, Gabriel Becker wrote:
> I cannot speak to initial intent, perhaps others can. I can say that
> there is at least one place where the difference between R_NilValue
> and NULL is very important as of right now. The current design of the
> ALTREP framework contract expects ALTREP methods that return a SEXP
> to return C NULL when they fail (or decline) to do the requested
> computation and the non-altclass-specific machinery should be run as
> a fallback. The places where ALTREP methods are plugged into the
> existing, general internals then check for C-NULL after attempting to
> fast-path the computation via ALTREP. Any non-C-NULL SEXP, including
> R_Nilvalue will be taken as an indication that the altrep-method
> succeeded and that SEXP is the resulting value, causing the fall-
> back
> machinery to be skipped.

This is helpful. Currently this will work in the low level SEXP API,
though not in the hand-holding level (and I think this is probably a
reasonable behavioural distinction); in the low level SEXP API in
rgo/sexp there are two facilitated ways to return values to R, the
Value.Pointer method and the Value.Export method, the first returns
whatever the value of the SEXP is, C NULL, R_NilValue or non-null
result, the second converts C NULL to R_NilValue before returning.
However, in line with the Go philosophy of not doing too much, the user
is free to return a Go nil (equivalent to a C NULL) or anything else if
they want.

The Pointer method is a pure type conversion:

```
func (v *T) Pointer() unsafe.Pointer {
	return unsafe.Pointer(v)
}
```

and the Export method was an addition I made when I accidentally
returned a nil during testing and the R runtime complained at me.

```
func (v *T) Export() unsafe.Pointer {
	if v == nil {
		return NilValue.Pointer()
	}
	return unsafe.Pointer(v)
}
```

These are really just helpers that mean users don't need to use the Go
unsafe package directly for anything other than making their function
signatures valid.

Similarly, the parameter passed in to Go can be C NULL, R_NilValue or a
non-null value. It's a little more work in the case that C NULL needs
to be distinquished from R_NilValue:

```
func UserGoCode(p unsafe.Pointer) unsafe.Pointer {
	if p == nil {
		// We have a C Null.
		// If this condition is omitted, v below will be
		// R_NilValue when p is nil.
	}
	v := (*sexp.Value)(p).Value()
	// We have v as a type that is one of the R TYPE values.
	...
```

> IIUC the system you described, this means that it would be impossible
> to implement (a fully general) ALTREP class in GO using your
> framework (at least for the method types that return SEXP and for
> which R_NilValue is a valid return value) because your code is unable
> to distinguish safely between the two. In practice in most currently
> existing methods, you wouldn't ever need to return R_NilValue, I
> wouldn't think.

This should be OK from what I've said above. What the user won't be
able to do is distinguish between C NULL and R_NilValue in values that
come from. So I guess a better phrasing of my original question is
whether valid SEXP value fields ever hold C NULL. If they do, then I
have a problem. I'm very much hoping that some kind of sanity in the
code prevails and this doesn't ever happen.

> The problem that jumps out at me is Extract_subset. Now I'd need to
> do some digging to be certain but there, for some types in some
> situations, it DOES seem like you might need to return the R-NULL and
> find yourself unable to do so.

I have not looked at all at ALTREP (though it looks like it would be
valuable given the goal of the project), but as above, I *can* return
the C NULL.

> Its also possible more methods will be added to the table in the
> future that would be problematic in light of that restrictrion.
>
> In particular, if ALTREP list/environment implementations were to
> ever be supported I would expect you to be dead in the water entirely
> in terms of building those as you'd find yourself entirely unable to
> implement the Basic Single-element getter machinery, I think.

Is this still a concern with my clarifications above?

thanks
Dan