In Rust, if you define your function interface as possibly returning an Error, then you might find a circumstance where you call a function whose error you wish to propagate up to become your function's error.
Here's how you'd traditionally write something like that, in a case where it potentially propagates an error from one of two calls:
fn read_username_from_file_traditional() -> Result<String, io::Error> {
let f = match File::open("username.txt") {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match f.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
But there's an application of "the ?
operator" (which doesn't really have another name) which will do this propagation for you.
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("username.txt")?.read_to_string(&mut username)?;
Ok(username)
}
Historically We Couldn't Do Things Like This
There was no way for such an operator to be able to know what the concept of RETURN was.
Hence you see things like:
change: combinator [
"Substitute a match with new data"
return: [~<change>~]
parser [action!]
replacer [action!] ; !!! How to say result is used here?
<local> replacement'
][
[^ remainder]: parser input except e -> [ ; first find end position
return raise e
]
[^replacement' #]: replacer input except e -> [
return raise e
]
; CHANGE returns tail, use as new remainder
;
remainder: change:part input (unmeta replacement') remainder
return ~<change>~
]
But Virtual Binding Means We COULD Do It
Let's say we were using ?
for this also. It could essentially have this behavior at the callsite:
let result: meta some-call xxx yyy zzz
if raised? unmeta result [return result]
result: unmeta result
So you could rewrite the above as:
change: combinator [
"Substitute a match with new data"
return: [~<change>~]
parser [action!]
replacer [action!] ; !!! How to say result is used here?
<local> replacement'
][
[^ remainder]: ? parser input ; first find end position
[^replacement' #]: ? replacer input
; CHANGE returns tail, use as new remainder
;
remainder: change:part input (unmeta replacement') remainder
return ~<change>~
]
That's awesome. I don't know that I love the ?
for the name, I'd probably like !!
better.
[^ remainder]: !! parser input ; first find end position
[^replacement' #]: !! replacer input
Though as the comment above shows, I use !!!
for attention. One exclamation point?
[^ remainder]: ! parser input ; first find end position
[^replacement' #]: ! replacer input
That's too slight. Something out of left field... maybe a visual indication of "pass this error up?"
[^ remainder]: --^ parser input ; first find end position
[^replacement' #]: --^ replacer input
Or something to indicate what you're passing up, like *
for "problem"?
[^ remainder]: *--^ parser input ; first find end position
[^replacement' #]: *--^ replacer input
Interesting looking ideas... but would be a caret-in-word exception. (There's not--I suppose--any particular reason why you can't have carets in words if they're not at the head...we allow it for tick marks.)
Nuance of Slashes I Realized
I've mentioned the goal that if you write something like not/even? that would act as if you had written not even?. I presumed that wouldn't be useful typically, but it would be useful if you were using a terminal slash, and trying to pass the cascade of functions as a single value somewhere, e.g.
>> match not/even?/ 7
== 7
BUT there's a rule in infix deferred processing that it runs "one expression evaluation to the left".
This means ?/foo
could be used to not disrupt the infix deferred logic. It's more general than just this case, which is awesome. But it definitely helps here.
I'll Write It Up, But It Needs a Name...
Suggestions, please!