In the post about lifetime of handles given back to C code, I brought up the tough problem of "who frees a floating REBVAL*
that the system has entrusted an API user with?"
So every time you run a rebDo()
you are getting back a pointer that the system has to count as a "live" reference... if it's a BLOCK!, there's no way to know a-priori how long the caller is going to be picking and poking values around in that block.
For now, all values get cells, and their lifetimes are managed. That includes INTEGER!. So let's take a simple example:
int AddOneToSomething(int something) {
REBVAL *somethingVal = rebInteger(something);
REBVAL *sum = rebDo("1 +", somethingVal, END);
int result = rebUnboxInteger(sum);
rebRelease(somethingVal);
rebRelease(sum);
return result;
}
It's a pain to have to write so many rebRelease()s. One of the answers is to have a default moment at which such things are GC'd automatically, and that's going to be possible sometimes--but not always, and it will mean leaving things alive longer than they would be freed up otherwise. Another answer is to use C++, where ren::Value
automatically knows when it's out of scope and can release things.
But a wilder cross-language answer came to mind, which applies to C, JavaScript, and anything else. What if all APIs that could take a single REBVAL* to process it, might also be variadic? We could see rebUnboxInteger()
as being a variant of rebDo(), instead of simply taking one argument:
int AddOneToSomething(int something) {
REBVAL *somethingVal = rebInteger(something);
int result = rebUnboxInteger("1 +", somethingVal, END);
rebRelease(somethingVal);
return result;
}
Now the rebDo() is folded into the rebUnboxInteger() call, and we've gotten rid of one userspace handle. That's one handle that doesn't need to be allocated, tracked, or freed. I've also proposed the idea of marking certain handles as releasable by the rebDo() mechanics once it sees them in the line of processing, like a rebT() instruction for marking things as "temporary":
int AddOneToSomething(int something) {
return rebUnboxInteger("1 +", rebT(rebInteger(something)), END);
}
We might even go so far as to say for something common like this, that rebI(...)
could be a shorthand for rebT(rebInteger(...)):
int AddOneToSomething(int something) {
return rebUnboxInteger("1 +", rebI(something), END);
}
An interesting point is that this is made more palatable because things like WORD! and FUNCTION! are not "live" by default. You don't want to instead of saying rebSpellingOf(someWord), rather rebSpellingOf(rebUneval(someWord), END);
!
(I should also point out that having to have END is something that @giuliolunati has already avoided in JavaScript, and could be avoided in C++...or even in C for C99 builds.)
But it seems to me on average, you're looking at enough savings on the total amount of code that even if you have to put END on, it's a win. If you don't need an END, it's seemingly kind of a slam-dunk win.
So basically any API that would perhaps otherwise have been seen as taking a plain REBVAL* would now take a variadic stream of string, value, and instruction components.
Examples:
-
instead of
rebUnboxLogic(REBVAL *logic_value)
...what aboutrebDid(...)
andrebNot(...)
? e.g.rebDid("all [", condition1, condition2, "]", END);
orrebNot("error?", value, END);
-
instead of
rebRelease(rebDo(...))
what aboutrebElide(...)
?