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

Reply via email to