What Are NULL, VOID, NONE, and TRASH ?

These are important "antiforms" you need to know about. (Antiforms can be held in variables, but cannot be put into lists like BLOCK!).

TRASH! is the antiform of RUNE!

Trashes are used to poison variables so they cause errors on WORD!-access

>> x: ~  ; no console output is printed for trash

>> x
** PANIC: x is TRASH! (~)

The simplest form of trash is the antiform of the space RUNE!. Its meta-representation is a quasiform shown as a lone tilde (~), which you can call "quasi space" or "quasi blank" if you like (I also call it a "quasar"). So evaluating quasiform space gives you antiform space, e.g. TRASH, which has no representation in the console.

>> quasi _
== ~

>> quasar? first [~]
== ~okay~  ; anti

>> ~

>> trash? ~
== ~okay~  ; anti

Operations like comparison functions do not accept unset states... as in Rebol2:

rebol2>> #[unset!] = 1
** Script Error: Cannot use equal? on unset! value

ren-c>> ~ = 1
** Error: VALUE1 argument of EQUAL? is unspecified

NONE is an Empty Splice, the "positive" sense of NULL

>> none 
== \~()~\  ; antiform (splice!) "none"

>> append [a b c] none
== [a b c]

It's truthy, but comes in handy in places you want to "opt in" to an operation with nothingness.

VOID is the unstable antiform of COMMA!

VOID is another name for the GHOST! antiform... a "ghostly" intent--with no stable value to offer, hence cannot be decayed. There's also a "heavy void" which is the behavior of an empty parameter PACK!, for when you want to convey no value but not vanish.

GHOST! vanishes in REDUCE, and act as a no-op for things like APPEND.

VOID is the "result" of eval []

>> eval []
== \~,~\  ; antiform (ghost!) "void"

>> ()
== \~,~\  ; antiform (ghost!) "void"

It vaporizes in COMPOSE/etc.

>> compose [a (if 10 > 20 ['b] else []) c]
== [a c]

>> reduce [1 + 2, if 10 > 20 [<nothing>] else [], 10 + 20]
== [3 30]

>> append [a b c] ()
== [a b c]

>> if 10 > 20 ['b] else []
== \~,~\  ; antiform (ghost!) "void"

While VOID and HEAVY VOID have similar behaviors, only VOID represents a "vanishing intent" in multi-step operations. Understanding the subtlety of the distinction--and how the system protects you from accidental vanishes--is explained elsewhere:

https://rebol.metaeducation.com/t/why-comment-vanishes-but-not-eval-of-comment/2563

NULL is the antiform of the WORD! "null".

In the API this is represented as the 0 pointer and does not require having its handle released, so it is like C's NULL. It is used as an "ornery nothing"...but unlike TRASH it can be fetched by normal WORD! access. The system accomplishes elegant error locality using the VOID-in-NULL-out protocol in many places, which hinges on the OPT function that converts NULL to VOID.

>> case [1 > 2 [<a>] 10 > 20 [<b>]]]
== \~null~\  ; antiform

>> reduce [1 + 2 case [1 > 2 [<a>] 10 > 20 [<b>]]] 10 + 20]
** Error: can't put ~null~ antiforms in blocks

>> reduce [1 + 2 opt case [1 > 2 [<a>] 10 > 20 [<b>]]] 10 + 20]
== [3 30]

>> third [d e]
** Script Error: cannot pick 3

>> try third [d e]
== \~null~\  ; antiform

>> append [a b c] try third [d e]
** Error: Cannot put ~null~ antiforms in blocks

>> compose [all your base (try third [d e]) are belong to us]
** Error: Cannot COMPOSE ~null~ antiforms into slots

>> opt third [d e]
== \~,~\  ; antiform (void!)

>> append [a b c] maybe third [d e]
== [a b c]

To Sum Up...

  • TRASH is used to poison variables

  • VOID is intentional emptiness--tolerated many places as meaning "I'd like to opt out please"--and it vanishes in some places It comes in a "light" vanishing form (antiform COMMA!) and a "heavy" form (empty antiform PACK!)

    • Since these opt out of aggregate conditional tests, they can't logically be acted on in an isolated conditional expression like IF
  • NULL is a signal, often meaning "I couldn't find what you were looking for"

    • Because it is a kind of "soft failure", it is the (only) conditionally false value

    • Also because it is a soft failure, most non-conditional slots reject it as an argument

    • OPT can be used tactically to convert NULL results to VOID

NULL and TRASH are stable antiforms... they can be held in normal variables, but won't be found in lists like BLOCK!.

VOID and HEAVY VOID are unstable antiforms, and can't be saved in normal variables--only meta-variables.

1 Like

F.A.Q.

How Did These Types Evolve From Rebol2 UNSET! + NONE! ?

See this thread.

Isn't It Simpler to unify empty-PACK! and TRASH! as UNSET!, like Rebol2?

One can reasonably ask if when all is considered, having fewer parts is better--even when more parts can be shown to have "a benefit here and there".

So this does hinge on how you feel about things like:

rebol2>> compose [a (print "b") c]
b
== [a c]

Personally I see too much downside to conflating "meaningless" values with "meaningful opting out". While you might be able to make excuses for an oversimplified example that's just PRINT like this one, when you imagine a more complex expression, an error is far preferable. And it gives you the room in the future to start giving back meaningful values, without breaking people's depending on the not-very-intentional opt-out behavior.

So in Ren-C you have to erase the trash value, with something like:

>> compose [a (elide print "b") c]
b
== [a c]

But another aspect is that as TRASH! became an antiform of any RUNE! (so not just ~ as the antiform of space rune), the messages became useful:

>> var: ~#[some message]#~
== ~#[some message]#~  ; antiform (trash!)

>> var
** Error: var is TRASH!: ~#[some message]#~

This turned out to be very useful.