The ? operator will be harmful to Rust

The RFC was discussed for a long time (it originated in 2014!) and accepted. That's pretty much the definition of "clear consensus" in the Rust community.

There will always be those who dislike it, but that's no different from many other language features/omissions that deviate from "common" expectations.

5 Likes

Back in 2014 Rust was still abusing sigils and had a runtime with green threads. Lots of time has passed since then.

Anyway, I just want to register my personal distaste for adding any hidden implicit functionality. Doubly so because it adds to the current confusing patchwork of disparate error handling techniques.

It’s also interesting that people are fighting against named arguments in a nearby thread on the grounds that it might introduce confusion. Even though the original proposals are mirroring the bog standard syntax present even in Ada and Fortran.

But here we have a truly non-standard and confusing operator that has very different semantic in other languages. Go figure…

1 Like

The RFC was merged this February and had literally hundreds of comments since 1.0, largely in the last few months leading up to the merge.

As @birkenfeld said, the RFC process, with all of those comments, is how we arrive at consensus for new features in the Rust community.

It's fine to bring up issues later (before the feature has been finally un-gated), but there's generally a high bar to clear (compelling new evidence that wasn't discussed in the original RFC thread) to decide to reject an implemented feature that went through the RFC process and was vigorously debated until the arguments reached a steady state.

You may be worried that the ? is too easy to miss, but it's not implicit. Exceptions are more implicit, because they create return points that are completely hidden from view. ? is an explicit way to propagate an error. It's fewer characters than try!(expr), but it's no less explicit.

7 Likes

Main issue thet I see with ? operator is that this is diverging operator. I mean that it contains hidden return in middle of the function. I would rather see it as a pipeline operator that push down the error. It would be similar to and_then and that sugar would be nice and would play quite good with current ecosystem.

Do you have the same issue with try!?

Having ?. as a pipeline operator that does map / and_then seems like it would be way more useful than a try operator. This way it’s familiar to everyone, it pipes down the error to the end, where you can handle it, and there’s no implicit returns. So it could roughly look like this:

let a = try!(x?.y?.z);

1 Like

Nothing that can't be solved with one search through the docs. Rust is already very verbose/explicit for the 'casual' user so reducing that through syntactic sugar is a great boon. The idea behind the ? is very simple so I think you're wrong on believing that it will hinder comprehension of the code.

Even if it is surprising (which I think it really isn't if you read the doc on it) the user shouldn't be protected from learning. I'm against the notion that the user should have a minimal learning curve for the sake of comfort, understanding the concept gives him more power to create better code. The idea behind ? is simple, if you have an error, return it, otherwise continue to the next function in the chain. That's not confusing, surprising or scary. If anything, the user should be happy that he doesn't have to handle each case explicitly, which for debugging he has the option to do but he should be able to skip all that.

3 Likes

I cannot stress how much I disagree with this. I have been actively using the ? operator for a long time now (ever since it first has landed in nightly) and it's great. It makes the code a lot more readable and encourages proper handling of errors.

My code as a result has removed almost all of the unwrap() code that I had around, particular in combination with the error_chain crate.

By hiding return statements in the middle of expressions, I believe Rust control flow will be very surprising to casual users of basically every other programming language.

In particular I strongly disagree with this. First of all the try! macro already exists and does exactly the same. Secondly I actually talked to lots of novices and the general opinion has been that something like ? cannot land fast enough because error handling is otherwise too complicated.

Making ? a safe navigate that does lots of stuff with options is primarily just going to have the effect that code that cares about performance is not going to use it and worse: it's going to eventually result in bad patterns to spread via crates. Safe navigation like in C#/JavaScript has no place in Rust.

23 Likes

Also I really, really do not like the fact that we’re reopening this discussion yet again. This has been discussed to death and back as far as I can see.

2 Likes

I find ? inconvenient when reading Rust code, but it doesn’t make things significally worse because reading Rust code is already inconvenient even without ?.

Imagine the task “highlight all the return points in a function”. In my practice this task occurs very often when reading the code and is important for understanding how function works, what it can return, what error conditions can happen and why.

For C/C++ code performing this task it’s very simple - use editor with “highlight all occurrences” ability and select one of returns. Now all function return points are highlighted (let’s ignore Rust panics and C++ exceptions here).

To perform the same task for Rust code I need to somehow find 1) explicit returns 2) implicit returns through trailing expressions 3) try!-s 4) ?-s. I guess with some editors it’s possible to hack up some special-purposed script for highlighting all of these, but in more general setup you have to 1) “highlight all occurrences” for return 2) “highlight all occurrences” for try 3) Ctrl+F or analogues for ?, “highlight all occurrences” don’t work for punctuation in editors I tried 3) Carefully read the function’s trailing expression (which can easily be a match with several arms). As you can imagine this all is not very convenient and productive.

1 Like

I guess with some editors it's possible to hack up some special-purposed script for highlighting all of these

How is it less work to highlight return but more work to highlight ?? Not sure I understand the concern here.

Let alone the fact that any macro can early return both in Rust and C++ and panics and exceptions exist in either as well.

Please read the whole comment and not that one sentence. I't 4 syntactically different (common) ways to return vs 1 way to return.

I read your entire comment and there are literally only three early returns in Rust: return and ? both of which are easy to spot as well as panic!. All of those are easy to highlight in an editor.

In C++ you have return and throw both of which are easy to highilght.

The argument about try! does not apply because at that point you also need to look into macros in C++ and there are plenty of macros which issue early returns. You can’t just bend the situation to prove a point. The same problem as applies to Rust equally applies to C and even more so to C++.

I mean, by that logic we can start talking about setjmp and longjmp as well which are regularly used for error handling in C. The entire Ruby interpreter as an example is full of it. Likewise the entire Python interpreter is full of custom returns hidden behind macros due to refcounting handling.

4 Likes

The equivalent C++ to a complicated trailing expression would be something like

auto result;
// complicated expression
return result;

That is still 1 return point.

We do want editors that highlight try!, ? and return, to catch scope exits. Having explicit returns on every early exit is so unergonomic that nobody but C does it - C++ would have an exception.

Macros with returns are outliers, they are rare* and confusing by default unless they are well known and idiomatic like try!. You can't go from try! to other returning macros and exotics like setjmp/longjmp "by that logic", there's no "that logic" to start with.

*Especially in C++. I don't use many third-party Rust libraries and not sure what kinds of macro abominations they may provide.

Anyway, we are losing the point. I'm not against ?, it's convenient in "surface reading" mode, that is more common, I'm telling that with or without ?, but with ? more so, Rust need good IDE/editor support to compensate for its numerous irregular control flow syntaxes.

2 Likes

Everyone here is talking about the semantics (early return, error handling, etc) while OP’s main complaint is syntactical ;).

Some counterpoints,

  1. Rust already has a precedent of having a syntax different from other languages: !123 means bitwise-not instead of logical negation.

  2. Rust’s ? returns an unwrapped value, while other languages’ ?. return wrapped values, so the type-checker will stop you from confusing the meanings when writing code.

2 Likes

C++ has exceptions, which are the mother of irregular control flow syntaxes. Not even an IDE can find them. Rust has panics, but they are not handled much differently from a SIGKILL (of course, the address space will not be destroyed, but the handling is similar).

2 Likes

I personally worked only with projects with very coarse grained exception handling, which felt more like panics rather than control flow (this is why I ignored panic/throw in my comparison above), but I agree that if exceptions are used as fine-grained control flow tool, then it's worse that anything that can be found in Rust.

While I agree with these comments for reading source code, I'm not sure we can meaningfully escape what it's actually doing when we teach it. I know that I at least thought it was clearest to teach try! as a mechanism for abstracting case analysis, control flow and type conversion simultaneously. I did it this way because it's a really clear progression from doing any one of those things manually (i.e., writing out the match, adding an explicit return or doing an explicit type conversion). If you're going to be using Result, ?, From and Error in your code, then you really need to know how they all work together, and I don't think we can gloss over it.

With that said, I don't really think implicit returns are a problem. They seem to be fine in the try! macro.

I will say that I don't really understand how ? is a huge improvement over try! (nor do I see how it is a huge loss, either), but lots of folks do insist that it simplifies error handling. If I think about it in terms of how much it shrinks the Error Handling chapter in the book though, I don't think there's much difference. Error handling is still a complex (and wonderful, especially in Rust, IMO) topic, with or without ?. That is, I personally think both sides of this debate are enormously overstated.

Either way, I think the community reached a consensus that we should adopt ? and we should now move forward and start debating the Carrier trait. Rehashing the RFC really won't do us much good unless there's some compelling new evidence (like, for example, lots of people tried it and hated it, but I'm not seeing that).

12 Likes

I think the most annoying point about try! is that is has function syntax, not method syntax. I'd prefer some kind of method syntax macros over ?, e.g something like:

let x = foo().!check();

The ?-syntax is just too easy to overlook IMO.

5 Likes