Labeled TRASH! Runes in The Wild

If you access an unset variable, you don't get a lot of information back:

>> state: ~

>> state
** Script Error: state is ~ antiform

That ~ is the quasiform/antiform of the space RUNE!:

>> quasi _
== ~

>> type of anti _
== ~{trash!}~  ; antiform (datatype)

But you're not just limited to the space RUNE! to make "unset" variables... because you can use any RUNE!.

>> state: ~#[INITIALIZE-STATE not called yet]#~

>> state
** Script Error: state is trash: ~#[INITIALIZE-STATE not called yet]#~

(This concept was originally called a "tripwire" when labeled trash was a distinct datatype from trash. But now that the _ is just the rune! for space, it unifies to where all antiform runes are just called TRASH!)

DEFAULT reacts to labeled trash like anything else, considering the variable to be "vacant"

>> vacant? $state
== ~okay~  ; anti

>> state: default [10 + 20]
== 30

>> state
== 30

Can Be Better Than A Meandering Comment!

A comment won't direct people to the relevant issue at runtime. But a well-chosen labeled trash can.

Compare:

; !!! UPARSE is not available in SYS because it is higher level.  We hack it
; up so that when %uparse.r runs it pokes itself into sys.util/parse
;
parse: ~

With:

parse: ~#[higher-level %uparse.r hasn't set SYS.UTIL/PARSE yet]#~

Much better! Gives you the information you need, when you need it!

(Note: I was inspired to this concept by a style of usage @rgchris had in his scripts, using TAG!s to provide guidance for what was supposed to be filled into certain slots. I just wanted to give that some teeth, to know that the variable was conceptually unset.)

2 Likes

Labeled Trash Catches Bugs Failing Functions Won't

Prior to the existence of labeled TRASH!, there were some functions in LIB for things like RETURN, CONTINUE, THROW, etc. to give you messages when you tried to use these definitional constructs in places that didn't provide them:

return: func [] [
    fail:blame "RETURN called when no generator is providing it" $return
]

continue: func [] [
    fail:blame "CONTINUE called when no loop is providing it" $return
]

throw: func [] [
    fail:blame "THROW called when no catch is providing it" $return
]

...

Passing the $return bound word as the :BLAME argument is a bit awkward to write. But it means the error will blame the callsite, showing the error there--instead of implicating the FAIL itself inside the stub function. That makes the error much more useful.

But this still makes it look like the functions are available. :frowning:

You won't get an error if you say return/ to try and get a RETURN function. You'll get a function--and it will pass the rule that things ending in slash must look up to functions--but it's not the kind of RETURN function you actually wanted.

This led to confusing downstream bugs when some code wasn't binding correctly, but proceeded merrily along as if it had gotten a RETURN function.

Labeled trash makes it nice and tidy!

return: ~#[RETURN used when no function generator is providing it]#~

continue: ~#[CONTINUE used when no loop is providing it]#~

throw: ~#[THROW used when no catch is providing it]#~

...

You get the "blame" of the callsite for free, because trashes aren't function instantiations.

And if you say return/ then that's an error, because it's not a function.

This is quite an improvement!

2 Likes

I've already shown that trashes are better for error locality than stub failure functions, because you get an error when you try to fetch a function e.g. via return/ ... vs. getting a function that's just going to fail later.

But there's another benefit over the stub function: it gives you the right error even if you don't have a refinement, vs. saying "no refinement available"

I noticed this when I had a function for adding things to the built-in SymId table during bootstrap:

add-sym: func [
    "Add SYM_XXX to enumeration"
    return: "position of an already existing symbol if found"
        [null? block!]
    text [word! text!]
    :relax "tolerate an already-added symbol"
][
    ...
]

After the table is finalized and written to disk, you don't want more stray calls to ADD-SYM. It's bootstrap so there's no labeled trash, so I just did the old crappy stub function trick:

add-sym: does [
   fail "Symbol table finalized, can't ADD-SYM at this point"
]

But what if you called ADD-SYM:RELAX ? If you use a refinement, you don't get the informative message, you get a message about it not having the refinement:

** Script Error: incompatible or duplicate refinement: :relax

So even though your stub function doesn't require any arguments, it has to copy all the refinements as well, or you don't get a good error!

With labeled trash, it would be all good:

add-sym: ~#[Symbol table finalized, can't ADD-SYM at this point]#~

Just Another Big W For TRASH! Runes :trophy:

They're kind of the sleeper hit of the isotope world...

1 Like