Syntax for returning early with an error

No, sorry for not being more specific. It can early return any type that implements Try. The desugaring is

excuse foo;
// becomes
return Try::from_error(From::from(foo))

except if it's in a try block, it only exits the try block, not the whole function.

Well, this won’t work with the new Try trait v2 anymore though, AFAICT.

Or rather, the “equivalent” would be to only support Result.

If try_trait_v2 is accepted, the desugaring becomes

return FromResidual::from_residual(foo)

why do you think this won't work?

because then foo would need to be of type Result<!, E> for e.g. a Result context. (Or foo: Option<!> for Options, etc..) You’d need to write excuse Err(...). I.e. the desugaring not “equivalent” to the suggested return Try::from_error(From::from(foo)) desugaring with the try-v1 trait.

I don’t think the ergonomics of try-v2 are built in a way that it’s usually not intended to manually provide values for the Residual type.

Hmm, the RFC explicitly says

This is forward-looking to be compatible with other features, like try {} blocks or yeet e expressions or Iterator::try_find , but the statuses of those features are not themselves impacted by this RFC.

One should question what exactly the “compatibility with yeet e” is supposed to mean.

Edit: Oh, wait there’s a paragraph about it hidden at the end of the thing!

So with the option

  • It could put the argument into a special residual type, so yeet e would desugar to something like FromResidual::from_residual(Yeeted(e)) .

one can archieve yeet-compatibility for any type Foo<Bar, T> by implementing

impl<T> FromResidual<Yeeted<T>> for Foo<Bar, T> {...}

I like this. One could add another type, e.g. YeetedNothing for supporting an argument-less yeet; call. Something like Option would implement this. The implementations for Result and Option would be:

impl<T, E> FromResidual<Yeeted<E>> for Result<T, E> {...}
impl<T> FromResidual<YeetedNothing> for Option<T> {...}
1 Like

Early return for success never became a streamlined thing. Assuming Try trait v2 goes through, people who want it will presumably make their own types so that ? returns the success case. One can also imagine implementations where the early return is neither an error or a success, really. (Arguably this is already the case for Option.)

So the colour of the yeet shed should probably be error/success/other agnostic.

This went poorly last time. I don't know if there was ever a resolution on whether pre-reserving keywords should or shouldn't be done, though. The team seemed split on the matter.

1 Like

This isn't inherently true. We could introduce such a keyword in an edition. This was an issue for try, for instance. And at one point I believe some error-handling crates used a throw macro as well.

I don't necessarily think it's the ideal choice, but it does have the advantage of not being specific to error-handling terminology.

Swift uses throw for its fallible function feature (which doesn't rely on exceptions) so there's at least some prior art on that. I think throw heavily implies an error though which doesn't seem desirable.

Of the listed key words bail expresses the intent the most clearly imo. I would personally rather have to update code to move away from an existing bail macro when moving to the new edition than have an ambiguous keyword, though I realize that may not turn out to be viable.

Another option might be combining return with the ? operator for return? foo since it would be replacing/enhancing return Err(foo)?. The difference between that and return foo? would probably not be obvious at first glance. It also doesn't really match the semantic meaning of ?. The only real benefit it has is dodging the issue of picking a new keyword.


You're right that bail would be possible, but I think it's a bad idea. The try macro could be replaced with ? automatically because it was part of the standard library, if I'm not mistaken. The bail! macros however can't be replaced by a bail keyword when running cargo fix. And ? was introduced long before try was reserved, so users had enough time to migrate.

Moreover, the new keyword has different semantics than current bail! macros (well, at least the one from anyhow) within try blocks. This could cause confusion.

I honestly wouldn't mind if yeet were the actual final keyword, and support the hypothetical #![feature(shitposts)] that would make it work even if it isn't

For those unaware, "yeet" can be roughly defined as "the opposite of yoink" and more formally defined as

an exclamation of excitement, approval, surprise, or all-around energy, often as issued when throwing something.

(I actually don't quite disagree with here and more only hear/use it as the verb opposite of yoink, but I'm only in one place with one friend group. They provide some more history of how the term emerged.)


Not commenting on the choice of name here, so assume for this post that we’re using yeet.

Assume we eventually want to use yeet e for

return FromResidual::from_residual(Yeeted(e))

(well, except in a try {} block where return is inaccurate).

An intermediate step could still be a function

fn yeet<E>(e: E) -> Yeet<E> { Yeet(e) }

returning an unstable/unnamable type Yeet together with an implementation

impl<E> Try for Yeet<E> {
    type Output = !;
    type Residual = Yeeted<E>;
    fn from_output(c: !) -> Self {
        match c {}
    fn branch(self) -> ControlFlow<Yeeted<E>, !> {
        let Yeet(e) = self;
impl<E> FromResidual for Yeet<E> {
    fn from_residual(Yeeted(e): Yeeted<E>) -> Self {

this could be introduced to the prelude, this way we can use yeet(e)? in place of yeet e until a keyword is eventually introduced. I think this is not too much of a loss after all, and the keyword can comfortably wait for the next edition (2024) without any problem. Once yeet keyword is introduced the r#yeet function can be deprecated.

To go into details why this approach works: the ? desugaring for yeet(e)? would be

match Try::branch(yeet(e)) {
    ControlFlow::Continue(v) => v,
    ControlFlow::Break(r) => return FromResidual::from_residual(r),

which, by inlining Yeet<E>::branch, reduces to

match ControlFlow::Break(Yeeted(e)) {
    ControlFlow::Continue(v) => v,
    ControlFlow::Break(r) => return FromResidual::from_residual(r),

which can be further simplified, back the original/intended yeet e desugaring

return FromResidual::from_residual(Yeeted(e));

so that the use yeet(e)? of a yeet-function is indeed fully equivalent to using the (hypothetical) yeet e keyword

(in all of the above, I’m still ignoring try {} blocks by using return; the argument still holds in the context of try block if you replace return with some hypothetical return_from_try in the desugarings for ? and yeet)

Edit: For some further considerations,

  • as already proposed in later comments in this thread, this means that there’s also the option of never actually introducing a new keword, instead just introducing a yeet function that is supposed to be used in yeet(e)?-style expressions;
  • the types Yeet and Yeeted above can be unified if we want, i.e. Yeeted could be its own residual type, although I’d probably prefer the name Yeet for the type in this case;
  • one can get rid of the yeet function, especially since a function yeet(e) that evaluates to Yeet(e) seems very redundant.
    • the two options there would be
      • either give the struct a lowercase name struct yeet<E>(E);
      • or use Yeet(e)?-style expressions (with a capitalized Yeet).
    • In my opinion, all three options (yeet function / lowercase tuple struct / use capitalized “Yeet(e)?”) have advantages and disadvantages.

I’ll reiterate my initial statement here: you have to replace the words yeet/yeeted with whatever verb we end up picking in all of the above.


What about oops?

fn foo() -> Result<(), String> {
   oops "something went wrong"

Minor nit: in the same fashion that return; is sugar for return (); and break; is "sugar" for break (); (when the latter is allowed: alas break (); is not allowed for for loops :disappointed:), the most intuitive / consistent approach for yeet; would thus be to stand for yeet ();, so that we'd have type YeetedNothing = Yeeted<()>;, at which point a struct Yeeted<Payload = ()> with a default type parameter would allow us to simply name it the Yeeted type.

1 Like

I know about the return; standing for return (); thing... I didn’t think like it’s the most ideomatic for yeet, since it would on one hand introduce special short syntax that can interact with Result<T, ()> (which is a type that e.g. clippy doesn’t seem to like very much), and on the other hand it allows for yeet (); in an Option context which seems weird IMO since nothing about Option says “I’m related to the () type”.

1 Like

Note that this was considered back in 2018, and it was decided then not to do keyword reservations going forward for features that have not yet been accepted:

The current conversation is about something like reserving k#keyword so that if yeet gets picked, it'll be usable as k#yeet until the next edition can reserve it fully.

1 Like

yeet (); or just yeet; would be useful in a function or try block that returns an Option<?> or ControlFlow<(), ?>.

You're totally right. I misread the situation (I assumed that this feature has already been discussed before, and that there already was a proposal for it, because the lang team had discussed it in April 2020, and the new Try trait RFC mentions it as well).

I'm not sure if I want to write an RFC for this, because I'm not very good at it. And even if someone else does, the RFC will probably not be accepted in time to reserve the keyword in the 2021 edition. That means the feature has to wait another 3 years, but the upside is that this gives us more than enough time to flesh out the details and bikeshed the name :wink:

I would however appreciate a bit more feedback, both positive and negative, because this forum doesn't have a downvote button, which makes it difficult to get an impression of how people feel about this. Is it worthwhile to pursue this idea, or am I just wasting my time?

The problem of this is of cause, it also implied "something is wrong" when early returning can mean a good result (especially when you are running a search algorithm and found something, rather than having to say "not found").

Having that said, I think the keyword that we currently already have is the break. We only need to find a way to define its scope better so it is not breaking anything.


I think there's plenty of interest in the overall idea, and figuring out how it should work is definitely valuable. Especially since, as was brought up, my RFC means there's at least two different ways the semantics could work.

Keyword conversations are annoying, but I'm liking a bunch of the conversation here about how it should be structured. Things like whether yeet foo or yeet Err(foo) or whatever, as well as conversations about whether it includes conversions -- does yeet 4 work for Result<_, u8>, for example?