libRebol tries to finesse its way around this as much as possible--and it has been doing an admirable
job. The idea is to make every "unboxing" routine an alternate interface to the evaluator. Hence you don't have to deal with getting a complex API handle object back when all you wanted out of a bunch of code was something like an integer or boolean. It drastically reduces the number of outstanding API handles in play.
Still, people are going to write:
let text = "etaf" reb.Elide("print [reverse", reb.Text(text), "]")
Today, that's a leak.
reb.Text() allocates a Rebol API handle. They didn't store it anywhere...they just passed it in to reb.Elide, so it's impossible to free with
reb.Release(). You have to say:
let text = "etaf" let handle = reb.Text(text) reb.Elide("print [reverse", handle, "]") reb.Release(handle)
There's a pretty cool trick of
reb.R(), shorthand for
reb.RELEASING(), e.g. a "releasing reference":
let text = "etaf" reb.Elide("print [reverse", reb.R(reb.Text(text)), "]")
This way the evaluator actually releases it as it passes by in the evaluation.
Then there's a shorthand for that combination of reb.R and reb.Text, called simply reb.T:
let text = "etaf" reb.Elide("print [reverse", reb.T(text), "]")
A little esoteric, but it's short! And saves a mouthful in C.
But let's be real.
Teaching the differences between reb.Text() and reb.T(), and explaining what the R in ArgR() means, is fighting a losing battle with new users. And if their programs crash in ways they can't debug, it will make the whole thing seem unreliable.
The good news is things have been set up to rarely need to create durable API handles at all. When I renamed reb.Run() to reb.Value() I thought I'd find a lot of cases, but there was just one in ReplPad, and zero in UI Builder. I'm facing a situation right now needing to create persistent API handles that outlast a function scope for the watchlist. First case that has actually needed it.
So I'm thinking we should not be scared to make API handles JS objects, that are tracked in a mapping table from handle number to object. Things like
reb.T() would still return raw heap addresses. But
reb.Value(), etc. would give JS objects that wrap up those addresses, that go bad at a deterministic time, and give clear errors if used after the point of death...but can GC otherwise.
Can function call completion be when handles die?
When a function call completes, the handles could be left alive but marked invalid due to the owning frame expiring--so attempts to use them will notice the expired frame and error. And when the GC pass goes through to clean up the expired frame, it will remove the object from the table, but tell it directly that it expired (so that if a use comes after the GC, you'll get an error). So the object lives as long as the user has a reference to it (which if no
reb.Release() is used, will be at least as long as the the period of time between the function call ending and a GC happening). But the GC sweep will pull the system's reference to that object so only the user references (if any) remain.
If you want a libRebol API handle to outlive the JS-AWAITER or JS-NATIVE it was allocated during, then you will have to "unmanage" it somehow, to extend its lifetime indefinitely until you ask to release it. Since this has turned out to be pretty infrequent, I think it's okay to make it the thing you have to do explicitly. If you don't, and try to use a handle outside its function, it would give you an error message directing you to
Then if this turned out to be inefficient for whatever case, there could be a mode where console messages tell you about places you didn't reb.Release() things. People who care could turn that feature on as part of a performance audit.
watches array holds them and then the JS can retrieve them later by index. That saves us on the design of any kind of reb.Unmanage() and the potential for leaking "invisibly"...you'd be seeing some physical Rebol map filling up in userspace. But it means every user would have to come up with their own ID scheme. There are pros and cons to this...maybe enough of a pro to say it's the way we do it until further notice.)
C will stay more or less how it is.
So there's no real way of doing what I propose above in C. You can mark a handle you've handed out a pointer to as being invalid. But then, when do you free() that stub? If you ever do, you'll run the risk of a client holding onto it crashing.
This means the parallel to the method above--of considering handles expired after the function that was on the stack when it ran ends--would have a crashier outcome. That's life in C.