Right now the only way you can get handles back from the API is as return results. This means if you want to extract N values, you have to make N calls:
REBVAL *a = rebRun("select", obj, "'a");
REBVAL *b = rebRun("select", obj, "'b");
But what if you could target API handle pointers directly, to get something like a SET-WORD!, except that the evaluator would write into an API handle pointer instead of an ordinary variable:
REBVAL *a;
REBVAL *b;
rebRun(
rebSink(&a), "select", obj, "'a",
rebSink(&b), "select", obj, "'b"
);
Why would this be useful?
It's often tricky to tunnel the variable your code wants out of a complex expression. Take the upcoming change to TRAP, for example. The TRAP only gives back an ERROR!, but what if you want to write your error handling code in Rebol...tunneling your value out of the trap?
REBVAL *data = ...;
rebElide("trap [",
rebSink(&data), "inflate/max", data, "uncompressed-size",
"] then [",
"info {^- -> failed [deflate]^/}",
"throw blank",
"]");
Note how this parallels data: inflate/max data uncompressed-size
. It's a lot easier than what you'd have to do otherwise in the C.
This is difficult to do safely...
One risk with something like this what happens if a SINK that's holding onto a pointer gets copied out somewhere and then used after that pointer is no longer valid. Consider:
REBVAL *Make_Evil_Block() {
REBVAL *local;
return rebRun("[", rebSink(&local), "10]");
}
rebRun("do", Make_Evil_Block());
What'll happen there is you get something parallel to local: 10, except it's writing to a local variable that's no longer on the stack when Make_Evil_Block() finishes.
A possible way to address this would be to require the variable to already exist so that it could become managed. So'd you'd pass a pointer to a REBVAL, not a pointer-to-a-pointer to a REBVAL:
REBVAL *Make_Evil_Block() {
REBVAL *local = rebBlank();
return rebRun("[", rebSink(local), "10]");
}
This would mean the GC would be responsible for making sure the API handle's memory slot remained valid until places it was referenced were gone. Hence a rebRelease() call wouldn't actually release it. That would mean the interpreter wouldn't crash if it hit a bad sink, it would just say C was done with that variable so you can't assign it.
Unfortunately this rules out sinking nulls. You'd have to at least TRY anything you'd sink, because from the API's point of view the only representation of a nulled cell is a nulled pointer.
I think doing it unsafely would be too risky. It's one thing to have a random C native someone wrote crash, and another to give the evaluator something it crashes on during an evaluation. So if this were implemented, it would be one of the motivating cases for mutable API handles, which has remained an open question.