On 27/04/13 18:51, Patrick Walton wrote:
On 4/27/13 8:49 AM, Lee Braiden wrote:
This would be a relatively ugly approach, to my way of thinking. Why
should a dead stream be returned at all, if the code to create it
failed? Why should I be able to call write() on something that could
not be created?
Two reasons:
1. If `open` returned a result type, you'd then have to call `.get()`
on it to achieve task failure. A lot of people dislike this approach.
Right, but this is why the (in this instance) exception model would be
better.
2. We have to have the concept of a "dead stream" or a "stream in the
error state" already, because the OS and standard libraries have this
concept. Given that, it seems simpler to just piggyback on that idea
rather than bifurcating the I/O methods into two: ones that return a
result and ones that set the error state on the stream.
No argument there, except that it might be nice to have a higher-level
API than the OS model.
By default that would cause a task failure.
Ah, I see.
Due to the fact that we don't know how many aliases there are to a
given stream, there's no way to force code that "breaks" a stream to
deal with it in such a way that it relinquishes all references to the
stream. (Actually, we could do it with unique types, but that would be
quite a burden -- you couldn't straightforwardly have a `@FileStream`,
for example.)
Only if you override it with a condition would it be possible to
continue after this
For instance, why should a try_read_uint exist, if you can:
// assign a handler for read failures
{ read(); }
// remove the handler
AND have that handler code implemented just once, in a library?
What does `read_uint` return if the handler wants to continue?
Right, I was thinking that Rust had a transaction sort of model for
retrying conditions, but it's just if worked else conditionhandler.
In that model, the handler should either solve the problem and continue,
or (eventually) throw.
To me, the extra keywords/work seem like a small price, for what we'd
gain in clarity, elegance, and flexibility. We'd have the best of two
popular error handling models, to choose from at will, with a nice
syntax, too.
This seems to basically just be exceptions.
No, it addresses both, as well as your performance concerns below.
While I agree that exceptions are a nice model from the programmer's
point of view (although Graydon strongly disagrees), I do have
concerns about the performance model. Exceptions are expensive. As a
language implementor, we basically have three, not very appealing,
options:
1. Burden every call site of every function call with checking an
error code, even if an exception was not thrown.
Right, horrible.
2. Burden every destructor with a call to `setjmp()`, even if an
exception is not thrown.
3. Use table-driven unwinding, which makes exceptions extremely
slow--so slow that programmers can't use them for simple "is there an
integer here?" queries.
No. My point is that we can provide the best of both models --
exceptions AND local conditions, and avoid some of the speed issues.
Iff we can provide inline, in-place condition code that either handles
the problem without examining complex exception objects, then we can
eliminate the performance costs, without exposing all the error handling
to users. But iff that can't be handled locally, the handler could
still throw, and allow a fallback to the exception model. I don't know
how implementable it is in Rust, but the model I suggested, or something
like it, would allow this optimisation, avoiding some of the stack
unwinds etc., while still allowing that when necessary.
It helps with things like "I'm trying to write data, and the disk is
full, but there are all these temporary files lying around." It helps
with parsing bad data in the sense that you could do a tight loop,
dropping mpeg frames until you get one you can parse, for instance, or
if you're re-reading a bad block up to N times before really throwing an
error.
If you have one character from a user or file, and it's as right as it
will ever be, but is still wrong, then you probably need to handle that
AS an error, then I can't see much option but to throw right back to the
UI, and incur the performance issues associated with that. But what
kind of future performance can you have, if your required input is
screwed beyond repair?
However, if you have a buffer of data, and you're guessing which
encoding it uses (say, endianness), then it would make sense to quickly
try the other encoding without throwing etc, but then still be able to
throw, if neither method produces a valid checksum. The inline handler
concept I talked about would allow that, with only performance costs in
the "neither is valid" scenario, and minimal performance costs even
then, if using the proxy approach I mentioned.
Also, by thinking of it in terms of /setting/ handlers, and thinking of
those handlers as reusable components, rather than closures coded each
time, we can have a much more readable, standardised, less noisy syntax
than the whole do condition.trap (|e| { code}) { real code } thing.
After talking with Brian some, though, I feel as though libraries
should provide `try_foo` methods only if it makes sense to do so. For
example, it probably doesn't make sense for HTTP parsing libraries to
supply a `try_parse_http` method. You only provide a `try` method if
both of these are true:
1. Programmers will want to recover extremely quickly from invalid input.
2. The function returns something other than unit. (Otherwise, a
condition handler is fine.)
That's a sensible breakdown of when it's necessary in the given model,
but I still think we should avoid the entire model, even if it requires
language changes.
--
Lee
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev