In thinking about how to handle I/O errors, I came up with a system that
needed a "do" that could take multiple closures (and pass them to
multiple trailing arguments). In thinking about it, this seems
incredibly useful (for non-I/O things), and consistent with how Rust
currently works.
I propose having "do" (or some related keyword) take an argument that
tells it how many closures it should consume, and pass to that many
trailing arguments. I'm thinking having do take a list of labels, which
are repeated at the blocks, for readability.
So if I don't like the if statement, I could write:
do(:if, :else) if_fun(my_condition) :if {
// if code
}
:else { // else code }
Python has a really useful "else" block on for loops:
do (:for, :else) for_else(~[1, 2,3]) :for |x| {
if something(x) {break};
}
:else{
// Run if "for" loop didn't break
}
Lisp has the "cond" statement, which is like a multi-if:
do(:0, :1, :2) cond(~[true, false, false]):0 {
// if zeroth element is true
}
:1 {
// if first is true
}
:2 {
// if second is true
}
This new "do" is basically the same as a regular do, except:
Do passes more than one block to the trailing arguments of its function.
Do wraps the return value in an option<X>, so we can detect a break;
(which returns None).
The I/O proposal I had is below, if anyone is interested. It has the
advantage of very clean, top-to-bottom code flow in the case where
errors are being handled. It has the disadvantage of adding one level of
parentheses for each I/O call.
I based this on the observation - in my code - that I/O errors are both
expected and handled, and generally not handled in a uniform way. You
simply can't avoid I/O errors - even if you check that a file exists, it
may not by the time you call open(). The same goes for network sockets,
except things are even more unpredictable.
Each I/O function would take two blocks - error and success. The error
block can return either 1. break - which stops processing, but lets the
task live; 2. io::fail, which kills the task; or 3. a good I/O handle,
which is passed to the success block.
do(:err, :succ) io::open("my-file"):err |e| {
if some-condition(e) {
do(:err, :succ) io::open("some-other-file")
:err |_| {io::fail}
:succ |x|{x}
}
}
:succ |f| {
do(:err, :succ) io::write(f) :err {io::fail}
:succ |f| { f.close(); } // close probably doesn't need to handle errors
}
On both error and success, code flows very nicely top-to-bottom
(skipping error blocks on success). It's easy to see what the argument
is to each success block - it's either the return value from the err
block, or the result of the I/O operation.
That said, the deep nesting makes it pretty ugly.
Thanks,
Erik
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev