On 04/25/2013 05:08 PM, Gábor Lehel wrote:
On Thu, Apr 25, 2013 at 10:00 PM, Brian Anderson
<[email protected] <mailto:[email protected]>> 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.)
It's almost taboo here, but we could consider adding catchable exceptions.
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...
Yes, the Option and NullFileStream approach are just two different ways
of saying the same thing.
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.
This is attractive, but how could you then create a block of code that
trapped *all* errors?
(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
Unfortunately there is no equivalent to `Typeable` yet in Rust, but we
have talked about it (called `Any`). I would also like `fail!()` take an
`Any`, so this seems like an appropriate place for it.
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev