Pre-RFC: flexible `try fn`

We agree that ergonomics is less important than readability, yes.

However, to confirm / be clear, I do not agree that pass + fail constitutes a more readable proposal than try fn + fail + Ok-wrap + .. does. I think that my proposal offers the best readability. :slight_smile:

Concretely, the confusion that break could cause is in:

loop { try { break; } }

Yeah, and it is specifically ("not ... always") that is the point that I think it truly muddies the water for control-flow. It would be surprising, to the say the least, to add a break mistakenly outside the block I intended (when cutting/pasting) and have it compile it to the equivalent of a return from the function when I expected it to break out of the block. This seems like a recipe for subtle errors that are difficult to track down.

2 Likes

@Centril How about to go another way around and make return to be used in try blocks instead of break, with added labels return 'label foo;? Thus try block will behave closure-like, and break will be strictly for loops only.

2 Likes

@Centril

Can you explain the motivation why you want to turn Rust into a very different language in regards to error handling? What is the actual problem this very aggressive change to the language tries to solve? Because I still didn’t see any motivation explanation besides somewhat hand-wavy „ergonomics“. And I’d like to point out that while I’ve seen many different things mentioned as the design goals of Rust, ergonomics was never one of these. On the syntax level, this seems like a much bigger change than async-await which arguably does have a motivation.

The approach to error handling „Nothing to see here, just an ordinary enum, like any other“ is kind of nice, because it is minimal congnitively. If I want to understand try fn with auto-wrapping, there several more layers to keep track of what happens. It makes the gaps between what I want to do, what is written and what actually happens larger (even if I don’t count the fact that the old syntax has to stay). But the bigger problem with the proposal is how different it is from what we have now.

The current users of Rust are using it because they like how it feels and how it works for them. Sure, some things can be improved a bit, but if they wanted a completely different language, they would not be using Rust in the first place. It feels a bit de-motivating to see proposals for „Oh, we’re turning Rust into something completely else. If you liked the old one, bad luck for you.“ and basically trading one set of users for different one. And I speak about individual people (me at last), I don’t even want to think how I’d go about trying to persuade my boss to use Rust in the next project if such a big changes land regularly. Sure, I’d still be able to compile the old code, but it would no longer be idiomatic, would spill bunch of warnings at me… I believe if Rust is to be called „stable“, it doesn’t mean only that the old code will compile (that is important), but also that if I take a break from using it for a year, I’ll not be starting from scratch later on. One of the big selling point of good old C is that it’s just that ‒ there were no big changes over the last 30 years.

To state in other words how strong aversion to this change I personally feel: over the many years of coding, I’ve taken programming languages as mere tools and none of them felt like a good one. I even thought about trying to design my own language and the only thing stopping me was the idea how big task it is. When I started to use Rust I finally felt like I found one that I like, that it’s a good match for my needs and likes. That it is something more than just a tool. Introducing this complicated, sometimes surprising syntax to pretend Result is something special while in fact it is value like anything else would turn the whole language into another mere tool and I’d have to continue searching. Sure, from the global point of the language, I’m just one person. But seeing how strongly some people (and it feels like about half of the people here) oppose it, I don’t think I’m completely alone.

29 Likes

See https://github.com/rust-lang/rust/issues/48594#issue-300765343 for discussion on label-break-value vs. label-return-value.

I just want to point to everyone that this thread has run completely away - more than 80 posts in less than a day, more than 50 of those in the last three hours. When you have a thread like this, its pretty difficult for anyone new to step into it later on. Please consider replying more moderately to one another. :slight_smile:

10 Likes

Doesn't that just indicate how controversial it is (tautology)? So, here I am adding to the problem with another comment to your comment that added to the problem, but, I think the real problem is is that it is "controversial" and so is creating "controversy". That's kind of the point of a discussion forum, no? Try to get some level of agreement? If there is this much commenting, then, doesn't that signal something? Is that important to consider?

6 Likes

A good avenue we’ve tried sometimes in the past in such situations is to ask people to write long form blog posts about their ideas in a space like this one, which tend to have more lasting value than such a fast-moving discussion. (That said, I don’t think now is the time for that; everyone on the Rust team is laser focused on the 2018 Edition right now).

14 Likes

Nearly 90% of the comments are by 7 users, and from my skimming the thread they cover a wide range of subjects; its hard to form any impression of the community from the fact that 7 users wrote about 80 posts in a day.

I’d also point out that it looks like the 50 or so posts made in the three hours I referred to are all by those 7 users (I just did a visual check, so I could have missed a post by a different user). Whereas several users who only have 1 post in the thread posted in the first 20 posts of this thread. This is a good example of how these kind of back and forth threads become rapidly inaccessible to people who are not a part of the frenzy.

For fast moving conversations, an ephemeral chat medium is a good way to engage with one another without the expectation that anyone else consume the information. To present complex ideas to the community as a whole, as @aturon says, blog posts are much more effective than posting in a thread.

9 Likes

@withoutboats While what you’re saying is true, I think we tried and succeeded for the most part to not repeat ourselves. It’s true that consensus has not been reached, but a lot of different opinions and criteria concerning the subject have been gathered. I think the posting rate will be slower now.

I’m going to second all the dissenting opinions here as well, which is something not inherently worthy of comment IMO, but I feel that there’s some unjustifiable assumptions in here with regard to this “happy path”/ “unhappy path” dichotomy. It is a false distinction, in my experience.

First, you have to handle both paths correctly regardless. You don’t have a correct program if you neglect one in favor of the other. The only reason I know of to separate these paths is if you happen to be writing high performance code and you can tolerate continued poor performance in response to some condition. But there we’re really talking about the “likely path” vs “unlikely path”.

For instance, I’m writing a parser combinator library right now, and return Err(...) is totally expected and normal code flow. It doesn’t want or need to be treated differently.

I also don’t understand what problem this proposal is meant to solve. If you want to return an Ok-wrapped value, I don’t see how anybody could do better than return Ok(value). (Or the obvious Err analog.)

10 Likes

I understand opposition to the proposal, but I don't get how it can be misinterpreted in such way.

In which way do we "neglect one in favor of the other"? This proposal, nor its variations do not make handling of errors in any way "incorrect", they still subject to the same type checks exactly as they currently do.

No, we are not talking about it. If you want such analogy, it's more about using return and tail expressions for "business data" channel and throw/fail for "something went wrong" channel, thus removing the need for explicit Ok and Err uses.

throw ...; in try fn will be a full equivalent to return Err(...); in usual fn and not "unnormal" in any way. throw already contains information about returning an error, so Err part will be redundant.

By making it just return value? Yes, there is a problem with non-locality, but arguably we already have a lot of non-locality on such scale, when you have to keep function signature in mind to read its body correctly.

3 Likes

syntax :bike:

Once this lands I will want to use it for all fallible functions, and that’s almost all functions. This effectively renames fn to try fn, which is not so great, because the syntax looks like a special case, although that will be the common case.

I liked fn foo() -> T throws E syntax much more, because it looks like a normal first-class syntax.

1 Like

Okay, I'm a new person here to agree with vorner: we have tons of RFCs that converts Rust to something different. It may be good for someone, but it'a disaster for the rest. The pros/cons analysis shows that these RFCs provide a very little value (e.g. throw is just a shortcut for return Err(x.into()), but make Result type much more different from other enums. I always feeling goog about Rust that there is almost no "special" types, except some like [T] or str. But this proposals make it much more different. Not to say that it becomes impossible to allow same language-level support for MyResult<T,E> and other types...

The main question here is actually "Should we make Result<T,E> first class citizen of the language itself or it may stay just a regular struct like any other?". We already have ? which works with Result only, while it makes sense to make it work with others structs like Option. I was always feeling bad about I have to use map or something to propagate Some(T) while it's much easier with Result and ?. Proposals like this just make things even worse.

9 Likes

My understanding was that you might be returning Result<T,E> or Option or SomeOtherEnumMonadicType<T,Failure> where you don't want to have to write different things depending on what is being returned, but, just return the T or E/Failure/None based on whether it is return or fail. Basically, unify it so no boiler-plate is required. Please, someone correct me if my understanding is off-base.

2 Likes

Please, before voicing an opinion, at least read the proposal. Because the following statements are factually wrong:

1 Like

I have read proposals carefully. I understand that there may be custom traits that provide same behavior for custom types. But it doesn’t work in practice.

For example, where is a trait to make ? work with custom types? There isn’t one.

Another question is why we have to add language burden to save bunch of characters and two braces? I see the immense value of async/await, it’s a must have feature that integrates well in the language and makes code clearer. And I see this proposal that allow to write the same code in the same manner using almost same characters, but which is a burder of extra language features you must design, implement, document, test and ship, as well as implement corresponding lints/RLS things/… And for what? With async/await I see the value where callback hell becomes a linear code where it’s clear what happens, with clean error handling, here I see the only desire to change the language in ways he sees it.

5 Likes

My understanding is that the "Try" trait, that is currently stabilizing, will do that: Try in std::ops - Rust

4 Likes

Again, factually wrong: Try in std::ops - Rust

For the same reason why we have ? operator instead of try!, about which it can be said "it saves bunch of characters and two braces". The answer is: because it's a very common, and even tiniest papercut when accumulated becomes quite painful.

1 Like

I don't think that is a fair analogy. The ? try Try! is different in that ? operator keeps you from having a bunch of nested Try!( Try!( .... ) ) an permits method chaining in a monadic style. I just don't think the comparison is valid.

7 Likes