[rust-dev] Update on I/O progress

Gábor Lehel illissius at gmail.com
Thu Apr 25 17:08:39 PDT 2013


On Thu, Apr 25, 2013 at 10:00 PM, Brian Anderson <banderson at mozilla.com>wrote:

> On 04/25/2013 07:18 AM, Gábor Lehel wrote:
>
>>
>> Whether to respond to failure by failing the task or by indicating it in
>> the return value seems like it would be better handled by having separate
>> functions for each. In the case where you expect success, the result
>> shouldn't be an Option. If the result is an Option, it shouldn't fail the
>> task (whether or not a condition handler is present). So for example:
>>
>> open<P: PathLike>(path: &P, mode: FileMode, access: FileAccess) ->
>> FileStream
>>
>> try_open<P: PathLike>(path: &P, mode: FileMode, access: FileAccess) ->
>> Option<FileStream>
>>
>> If the expect-success version of a function returns (), the try_ version
>> would return bool rather than Option<()> (which are isomorphic).
>>
>> Upon encountering an error, open() would raise a condition, and then
>> continue if possible, otherwise fail!().
>>
>> I'm not sure whether try_open() would raise a condition or not. The way
>> conditions were described, if they aren't handled, the task fails.
>> try_open() definitely shouldn't fail. Would it be reasonable to raise a
>> condition and instead of failing, return None if it's not handled? If not,
>> then try_open() shouldn't raise any conditions, and should just return None.
>>
>> The obvious drawback is twice as many functions, but it feels preferable
>> to forcing two different behaviours onto the same function. The fast
>> (expect-success) path would be just as clean as before, if not cleaner.
>>
>> [1] https://mail.mozilla.org/pipermail/rust-dev/2012-October/002545.html
>>
>
> With this strategy could we make the `try_` return values return
> `Result<FileStream, SomeErrorType>` (not using conditions at all)?


Don't see why not. The other option would be to return Option and provide a
(task-local) function to retrieve the details of the last error (I think I
saw something like that mentioned somewhere). I don't have a clear
preference.



> Then nil-returning functions would need to be `Option<SomeErrorType>`.
> Having two ways to perform every I/O operation will 'infect' all code that
> uses I/O indirectly. For instance, we have a `ReaderUtil` trait that
> defines maybe two dozen methods. Do those all get duplicated? That seems
> pretty disastrous to me.
>

That's a good point. If all higher-up IO libraries end up having to write
two versions of everything too, that's pretty bad.

Ideally, you'd want to have everything do it one way consistently, and
provide some convenient method to translate it to the other way. But if the
default is condition-raising/fail!()ing, then the translation to Option
involves spawning a task which is unacceptably expensive, and if the
default is Option, then the translation to fail!() involves mucking with
Options, which makes straightline expect-success code unacceptably ugly. It
feels like a failure of the language if there's no adequate way to abstract
this out, honestly, I don't know what else to say. None of the options look
appealing. (If you have to abuse conditions and Options in counteridiomatic
ways to cram both behaviours into half the functions in a workable way,
that doesn't reflect well on the language either.)

If the recoverable/substitute-providing condition raising versions can be
done satisfactorally, would be that remove the need for the try_ versions?
I think you'd still want some way to say "okay, give up on opening the
file, let's do something else instead", without failing the task. Which is
presumably what NullFileStream would be about. But removing these kinds of
hidden error conditions is what Option is supposed to be about. Going in
circles...


> You mentioned disliking the giant IoError enum, and I agree, but I don't
> know a better way to represent it. We don't have subclassing to create an
> Exception type hierarchy. Do you have any suggestions?
>

I was only thinking that instead of

condition! { io_error: super::IoError -> (); }

you might have

condition! { file_not_found_error: super::FileNotFoundError -> Foo; }
condition! { file_permission_error: super::FilePermissionError -> Bar; }

and so forth. In other words, instead of the branching in the IoError enum,
branching at the condition and error-describing-type level. That only makes
sense in the as-you-would-expect-a-condition-system-to-work scenario though.

(FWIW, it might be possible to emulate an exception hierarchy
Haskell-style[1] like:

trait Exception: Typeable {
    fn to_exception<'lt>(&'lt self) -> &'lt Exception;
    fn from_exception<'lt>(&'lt Exception) -> Option<&'lt Self>;
}

trait Typeable { // does anything like this exist already?
    fn type_of(&self) -> TypeRep;
}

impl Eq for TypeRep { ... }

fn cast<T: Typeable, U: Typeable, 'lt>(x: &'lt T) -> Option<&'lt U> {
unsafe { ... } }
fn cast_obj<U: Typeable, 'lt>(x: &'lt Typeable) -> Option<&'lt U> { unsafe
{ ... } }

Dunno if I got self/Self and lifetimes and all of that right.

IOW an Exception object can wrap any exception (to_exception) and you can
test whether it's an instance of any particular exception with
from_exception (which would typically be implemented with cast_obj). And
you can make similar traits further down the 'hierarchy'. Don't know if
this would be any good, just saying.)

[1]
http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html


> -Brian
>
>


-- 
Your ship was destroyed in a monadic eruption.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20130426/bd38c4da/attachment-0001.html>


More information about the Rust-dev mailing list