In the post about lifetime of handles given back to C code, I brought up the tough problem of "who frees a floating Value*
that the system has entrusted an API user with?"
So every time you run a rebValue()
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) {
Value* somethingVal = rebInteger(something);
Value* sum = rebValue("1 +", somethingVal);
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::ValuePointer
is a smart pointer class, and 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 Value* to process it, might also be variadic? We could see rebUnboxInteger()
as being a variant of rebValue(), instead of simply taking one argument:
int AddOneToSomething(int something) {
Value* somethingVal = rebInteger(something);
int result = rebUnboxInteger("1 +", somethingVal);
rebRelease(somethingVal);
return result;
}
Now the rebValue() 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 rebValue() mechanics once it sees them in the line of processing, like a rebR() instruction for marking things as being auto-released by the variadic traversal:
int AddOneToSomething(int something) {
return rebUnboxInteger("1 +", rebR(rebInteger(something)));
}
We might even go so far as to say for something common like this, that rebI(...)
could be a shorthand for rebR(rebInteger(...)):
int AddOneToSomething(int something) {
return rebUnboxInteger("1 +", rebI(something));
}
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));
!
So basically any API that would perhaps otherwise have been seen as taking a plain Value* would now take a variadic stream of string, value, and instruction components.
Examples:
-
instead of
rebUnboxLogic(Value* logic_value)
...what aboutrebDid(...)
andrebNot(...)
? e.g.rebDid("all [", condition1, condition2, "]");
orrebNot("error?", value);
-
instead of
rebRelease(rebRun(...))
what aboutrebElide(...)
?