What Should Returning a String From a Native Do?

I've raved about how cool rebFunction() is... though it may seem like mundane C interop, it's not. It's quite novel.

But to recap, a trivial example:

const char* Add_1000_Spec = "[ \
    -{Adds 1000 to whatever you pass in}- \
    return: [integer!] \
    x [integer!] \
]";
Bounce Add_1000_Impl(Context* binding)
{
    int x = rebUnboxInteger("x");
    int bigger = x + 1000;
    return rebInteger(bigger);
}

Value* action = rebFunction(Add_1000_Spec, &Add_1000_Impl);

So here you see us returning a synthesized Rebol Value, an INTEGER!.

You can return other things, such as rebDelegate("..."); which defers execution to code which can do all kinds of things like throw or raise errors, or produce unstable antiforms.

But No Return States Overlap UTF-8 Strings

This is by design. All Cells and Stubs in the system are out-of-band from valid UTF-8 leading characters.

This means that the meaning for this is wide open:

const char* Return_Utf8_Spec = "[ \
    -{return of a C const char literal pointer}- \
    return: [???] -{what do you think?}- \
]";
Bounce Return_Utf8_Impl(Context* binding)
{
    return "😸 Some UTF-8 Here 😸";
}

"Obvious" Answer: Return TEXT!

You might think that it's completely obvious what should happen in this case: that it returns TEXT! and that's the end of that.

Hence this would be a synonym for:

return rebText("😸 Some UTF-8 Here 😸");

Given that we could make the "instructions" legal to return as well, it could be equivalent to something even shorter:

return rebT("😸 Some UTF-8 Here 😸");

So this doesn't really rock anyone's world with a capability they didn't already have.

OR... It Could Run Code...

I'll point out that plain C strings aren't generally seen in the API as being equivalent to text literals. They're equivalent to code.

rebElide("this isn't a string -{but this is}-");

So shifting into non-code interpretation requires some kind of marker.

By this logic, we might say that the specific relationship between return and a string runs code...but more than that, delegated code.

This is to say that because you are returning, you can do things safely that you couldn't do otherwise... because you've passed control back to the trampoline. Hence something like a FAIL would not cross your own C stack levels.

return "fail -{This would be safe!}-"

It's not as powerful as rebDelegate(), because you can't splice things in variadically. And you might wonder how it could work at all...since it doesn't have a way to capture the shadowed binding variable.

But interestingly, it doesn't need to capture the shadowed variable, because in processing the return it's actually the point of control of the Dispatcher that passed the variable in the first place!

So indeed, you have access to the full context...including the local variables in the frame.

const char* Return_Delegates_Spec = "[ \
    -{Demonstrate returning UTF-8 as delegation}- \
    return: [tag!] \
    x [integer!] \
]";
Bounce Return_Delegates_Impl(Context* binding)
{
    if (rebUnboxInteger("x") > 1020)
         return rebValue("<nice large number!>");
    return "raise [-{X value is too small:}- x]";
}

rebDelegate() Still Is A Lot More Full-Featured

Not only can you splice, but you can use commas to separate runs of text:

return rebDelegate(
    "you", "can",
    "do", "this"
    "and splice", values, "in the midst"
);

That doesn't wind up doing anything weird like giving you "cando". The commas in the variadic are token stops.

C++ offers raw strings and you can do things like:

return R"(fail [
    -{You can write a really long failure message here}-
    -{And the raw string will do the right thing}-
])"

I'm used to the commas, but maybe that's an improvement over:

return rebDelegate("fail [",
    "-{You can write a really long failure message here}-",
    "-{And the raw string will do the right thing}-"
"]");

Overall, I Think String-As-Delegation Is The Winning Choice

Making a TEXT! string is both counter to the default interpretation of C strings, and will have a well-known shorthand.

I don't think return rebD() for rebDelegate() has any particular clarity to it.

Where I see this being most useful is for short failure messages, which are pretty common to be returned...even in the middle of functions.

But it also lets you do things you couldn't otherwise, like return "quit 1" or return "halt".

If you want to return nothing, you can just say return "~" instead of return rebNothing()... we can make that particular case fast where it doesn't call the evaluator (and maybe some other cases too...)

Really, it's just a greater force multiplier!