rebSpell() or rebBytes() of NULL in the API

For safety reasons, extraction APIs like rebSpell() and rebBytes() would raise errors if the evaluative product was NULL:

char* my_c_string = rebSpell("third [a b]");  // !!! fail/panic/crash

To get around this, the old way of thinking was you would use TRY and it would produce a BLANK!. The blank would be interpreted as an intentional nothing, and suppress an error:

char* my_c_string = rebSpell("try third [a b]");  // was like rebSpell("_")
assert(my_c_string == nullptr);  // this is what you'd get

But now we're in the era of NULL-in-TRY-out. Nothing gets converted to blank anymore...a TRY just gives back NULL. So it won't work this way.

What Was Safer About The Old Way?

You might ask what the difference is. Like if you're writing JavaScript and get a null back you don't expect, the JavaScript will raise an error when you try to use it where you shouldn't. Why's it better to make reb.Spell() itself raise the error?

Okay, yes, in JavaScript there's not really all that strong an argument. But in C if you use a null incorrectly, you get a crash. Intercepting it in rebSpell() gives a chance to throw (via longjmp, or now maybe C++ throw) to the error handler of the console...and have it be a non-fatal error.

(...though in WebAssembly, it's different... (!) I noticed something started to happen once we switched to the new C++ throw builds for the web console, and it's actually recovering from access violations in the native code and the console stays running! Probably not great to rely on this, but I do find it interesting.)

I Was Always On The Fence on This...

If you look at the design ethos of C, it really does kind of try to make it easy to sweep nulls under the rug.

For instance, if you call free() on a null pointer, it is a no-op.

Whether you agree with that decision or not, it's there...and so when you ask for a memory backing buffer (e.g. one that can be used to build a BINARY!) the rebMalloc()/rebFree() pairs are such that rebFree() accepts null. If it didn't do so, you couldn't pass it in as your memory allocator to things like zlib that expect you to obey the malloc()/free() behavior.

So I often wondered if rebSpell() and rebBytes() should be null tolerant, and let the chips fall where they may.

But...hrm. I don't know. I like the error emerging from inside the call.

What About rebTrySpell() and rebTryBytes() ?

With the new meaning of TRY, this actually makes sense!

It's a better way to bias it than to say that rebSpell() can give back NULL, where you'd use rebMustSpell() if you want to ensure it isn't null.

Very well, then. I'm glad we had this talk.