Skip to content

RFC: throw expressions #2426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
17 changes: 17 additions & 0 deletions text/0000-throw-expr.md
Original file line number Diff line number Diff line change
@@ -332,6 +332,23 @@ As always, when a new real keyword is introduced, there is some degree of breaka
This is a drawback of this proposal. A mitigating factor is that the degree is
believed to be small.

## *"But what about the success case?"*

If we introduce `throw expr`, then an obvious question becomes:
\- *"How do you do an early return with Ok-wrapping?"*
Not being able to perform an early return for the success case could
be considered a drawback. Although that could also be considered letting
the perfect be the enemy of the good.

A strategy for introducing early-return on success with Ok-wrapping **could**
be to introduce `try fn` and / or to let `return` perform an early return
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, go the other way around- let people break out of try blocks. On its own, this is probably a bad idea, as try blocks are not loops, but it's possible 'label: try { .. break 'label value; .. } would be sufficient. For that matter, maybe any return or break that leaves the try block should be Ok-wrapped? For example:

fn f() -> Result<T, U> {
    try {
        let t: T = get_a_t()?;
        return t;
    }
}

Maybe that's even worse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main concern wrt. re-purposing break is this:

try {
    // ..
    loop {
        // Are we breaking the loop or the try?
        // Perhaps it is obvious to say "the loop", but is it really?
        break 3;
    }
    // ..
}

Maybe that's even worse.

Probably, yeah.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding break with label:

'label: try {
    ..
    break 'label value;
    ..
}

That seems workable, but it is not particularly ergonomic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I'm a fan of having label-break-value, I see it mostly as a way for macros to provide customized flow control constructs. I'd rather it typically be internal to libraries more than something that shows up in syntax that would be used broadly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another, possibly bad, idea to facilitate for macros could be to introduce the special label 'try which always refers to the closest try { .. } block.

That way, you could just write:

try {
    ok!(value);
}

where ok!(value) expands to break 'try Try::from_ok(value).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Centril I think my rigorous reason is I don't like introducing new names "out of thin air." Every other label has its name chosen by the source code- I would be much less bothered by some kind of 'my_label: fn f() { ... } or 'my_label: try { ... } than I am by introducing 'fn or 'try as "special" names.

A similar-but-different situation came up in #2115, where people felt wary about allowing the source code to introduce names without some primary declaration point. But in that case, the source code is still the thing determining the name- you write the same name in two places and they mean the same thing. Labels inherently have a "declaration" point, but the same reasoning applies- you write the name down in both places, as a label.

(@squishy-clouds 'static is not a label at all- it's a lifetime, and further kind of a "monoidal identity element" like 0 or 1 or [], so it gets a bit of a pass here.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rpjohnst I don't think they would be so "out of thin air";

If the system of having special labels is applied out consistently per control flow block-form you could have:

  • 'fn
  • 'loop
    • 'while
    • 'for
    • maybe just merge these as one loop?
  • 'if
  • 'match
  • 'try
  • 'async
  • 'const (maybe not?)

The goal of these are not to be used directly in people's code, but rather hidden away in macros so that you can invent new and interesting DSLs. The problem with having to explicitly declare the labels is that you have to pass them into the macros.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any actual use cases for a generalized "magic labels" feature like this, even in DSLs? I've never heard anyone suggest any of these except as an implementation detail of a throw/fail macro.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One use-case might be that if we had a 'try built-in label that can be targeted by ?, I think all of the other exception like syntax can be implemented by the eco-system (or even std if deemed worthy) as macros. That would allow for:

  • People to use break-with-value and ? with these blocks without auto-wrapping or any other additional functionality, basically keeping it as close to current control flow as possible.
  • Experimentation with more exception like syntax on crates.io. Before things would settle that would also provide an explicit opt-in to specific semantics. Once things have settled, the functionality can simply be added to std. Since they would still be macros, there's still an opt-in to additional semantics like auto-converting a final result. Adjusting pieces of std also seems easier and more future proof than changing the language itself.

I'm wondering if it would make sense to put together a proposal for this path for the new exception like syntax extensions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any actual use cases for a generalized "magic labels" feature like this, even in DSLs?

Not very concretely at least; Just hypothesizing ;)

to the nearest `try { .. }` block and Ok-wrap.
In the latter case, the user loses the ability to `return` to the function.
This could be solved with `break 'fn expr`.
One problem with modifying what `return` means in `try { .. }` could be that
people are so used to `return` always returning from the current function
that it could become quite confusing.

# Rationale and alternatives
[alternatives]: #rationale-and-alternatives