ah true. Okay then I guess I’m back to preferring unwrap_or_fail
, or unwrap_or_abort
(or whatever fail!
is renamed to, if it is renamed).
In a language with nullable pointers or references you might write: (using C syntax for example’s sake)
void my_func(Foo* foo) {
assert(foo);
do_stuff_with(*foo);
}
If, in Rust, we phrase that as:
fn my_func(foo: Option<&Foo>) {
let foo = foo.assert();
do_stuff_with(*foo);
}
you can think of this as assert()
returning evidence of the truth of the assertion (if it’s true; otherwise of course it won’t return). I think this fits in very nicely with the broader philosophy of “helping the compiler help you” by exposing your reasoning to it with types and evidence.
In the C example, the only connection between assert(foo)
and do_stuff_with(*foo)
is in the programmer’s head. In the Rust example, the compiler knows about it, and will let you know if you get it wrong.
(So in case it’s not obvious: I’m taking the perspective that assert()
is not a new name for unwrap()
, but is actually an assert in the traditional sense, which has been improved to return useful evidence.)
And there’s actually prior art for this: in a dependently typed language like ATS, an assert(foo)
actually does let the type system know that in the following code, foo
can be assumed to hold true (otherwise the program would not still be running). (In pseudo-Rust we could give it a type like fn assert(statement: Proposition) -> proof(statement)
.) Now obviously Rust doesn’t have dependent types, so unfortunately we can’t do this in the general case, but this (Option::assert()
) is a specific case where we can.
I’m not sure where I originally encountered this behavior of assert
in ATS; right now this is the best reference I could find: http://bluishcoder.co.nz/2011/04/24/converting-c-programs-to-ats.html (ctrl-f assert)
I posted this on the pull request, but to reiterate here:
While I don’t feel terribly strong feelings about it, I do feel that using assert
for this purpose feels odd, and would prefer unwrap_or fail
or unwrap_or_abort
, depending on the result of that discussion.
Furthermore, if we really want to discourage this pattern, I’d be okay with removing the method altogether and making the programmer type .unwrap_or_else(|| fail!())
. This has the added advantage of allowing a custom message:
let file = file.unwrap_or_else(|| fail!("Cannot continue: failed to open file"));
For iterators, I like iter
and iter_mut
, but would prefer iter_move
to iter_owned
.
The advantage is already possible with .expect("Message")
.
Right. I think if we got rid of unwrap altogether, it would make sense to get rid of expect
as well. I guess I meant the advantage would be to encourage a descriptive message, because the programmer would already be typing fail!()
anyway.
Edit: It also allows doing something like
let file = file.unwrap_or_else(|e| fail!("Error opening file: {}", e))
I like get_or_fail()
or or_fail()
instead of assert()
, but all three options are better than unwrap()
It would be nice to put assert()
in a separate trait, so I can write
fn foo<T, A: Assert<T>>(x: A)
It would be nice to have expect()
for Result
. This method is useful for small script-like programs where everything happens in main()
. Ideally it should write the text provided by the programmer as well as the error message, E
(on a separate line).
If the closure syntax allows then it would be nice to replace all instances of unwrap_or(x)
with unwrap_or_else(|| x)
. Subsequently unwrap_or_else(|| x)
could be renamed into unwrap_or(|| x)
( or even into get_or(|| x)
). Similarly and(x)
could be replaced by and_then(||x)
which could be renamed into and(|| x)
.
If the long term goal is to support the above, then it might be practical to aim for future proof method names. Here is an proposal for renaming some methods:
unwrap_or_else -> unwrap_or (alternative name: get_or)
unwrap_or -> unwrap_or_value (#[unstable] alternative name: get_or_value)
and_then -> and
and -> and_value (#[unstable])
or_else -> or
or -> or_value (#[unstable])
I like this idea, since it would simplify the interface further.
I agree with removing the method altogether. It should be more of a pain to fail on a None than to supply a reasonable default.
foo.unwrap_or_else(|| fail());
does the same thing as foo.unwrap()
so why do we need the extra method?
I disagree, it’s exceptionally useful when you know that it must be Some. A pretty common situation I find myself in is doing some work to ensure that there is a value somewhere, and then subsequently retrieving the value by-reference from the Option to manipulate it further once the invariant has been established. There is no need to dance around failure here. If the Option is somehow still None, my program is absolutely incorrect, and I want it to fail.
my code does exactly the same thing as the current unwrap()
Codifying common patterns as their own metthods is valuable, in my opinion. foo.assert()/unwrap()
I can instantly recognize, foo.unwrap_or_else(|| fail());
takes me a bit to parse, and leaves room for copy-paste or reading errors.
then make your own macro unwrap!()
that does this
shouldn’t syntactic convenience be left up to the macros? It makes no sense having a large amount of standard library functions that are convenience functions because after a while most people wouldn’t be able to find the most convenient option anyway because there’s just too many ways of doing the same thing
Keeping one of the most popular convenience functions in all of Rust, currently used some 2,000 times in rust/src/lib*, is hardly equivalent to having a large number of them. (Although I don’t think skimping on convenience functions is a good idea either…)
edit: Also, I don’t see why a macro would be preferable to a method, except perhaps for better line number information.
Personally, I think Option::assert() or Option::or_fail() would both be fine. I do however, think there is a big problem with Option::expect(“Error message”).
When I first read the example rust code in the guide and saw:
let input = std::io::stdin().read_line().ok().expect("Failed to read line");
I thought it read pretty poorly. It reads as though we’re expecting the read line to be “Failed to read line”. I’d suggest, along the lines of the assert() replacement suggestions, something like the following:
let input = std::io::stdin().read_line().ok().or_fail("Failed to read line");
Both assert
and or_fail
do NOT explicitly indicate that it “returns a value”, which hurt the readability.
+1, this is also how I look at it. I like assert
, particularly given that assertions in Rust in general are never compiled out. I could live with ensure
or or_fail
.
But wait, didn't we settle this already? I can't remember anymore.
Same here, for sure.
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.