Skip to content
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

Tracking Issue for experimental yeet expressions (feature(yeet_expr)) #96373

Open
6 tasks
scottmcm opened this issue Apr 25, 2022 · 25 comments
Open
6 tasks

Tracking Issue for experimental yeet expressions (feature(yeet_expr)) #96373

scottmcm opened this issue Apr 25, 2022 · 25 comments
Labels
C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@scottmcm
Copy link
Member

scottmcm commented Apr 25, 2022

This is a tracking issue for experimenting with the "throw expression" idea from RFC#0243.
The feature gate for the issue is #![feature(yeet_expr)].

Per the lang process, this cannot go further than experimenting without an approved RFC -- and will certainly not stabilize under the name yeet. Please try this out and give experience reports, but be aware that it may well change drastically or be removed entirely.

Currently the primary purpose of the feature is to ensure that redesigns of the Try trait (#84277) are compatible with potentially doing this in the future, and secondarily to experiment with whether this is worth having as an expression, or whether it should be removed in favour of a pure-library approach.

Because the do yeet syntax is explicitly temporary, do not expect various ecosystem tools to support it, especially not those which have stability promises.

Lang initiative: rust-lang/lang-team#160
Tracking issue for standard library additions: #96374

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

  • Is this even worth having syntax for?
  • What's the right keyword for it? yeet is used for now to avoid priming any particular choice.
  • How should this work semantically?

Implementation history

@scottmcm scottmcm added the C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. label Apr 25, 2022
bors added a commit to rust-lang-ci/rust that referenced this issue May 1, 2022
Add `do yeet` expressions to allow experimentation in nightly

Two main goals for this:
- Ensure that trait restructuring in rust-lang#84277 (comment) doesn't accidentally close us off from the possibility of doing this in future, as sketched in https://rust-lang.github.io/rfcs/3058-try-trait-v2.html#possibilities-for-yeet
- Experiment with the *existence* of syntax for this, to be able to weight the syntax-vs-library tradeoffs better than we can right now.  Notably the syntax (with `do`) and name in this PR are not intended as candidates for stabilization, but they make a good v0 PR for adding this with minimal impact to compiler maintenance or priming one possible name choice over another.

r? `@oli-obk`
The lang `second` for doing this: rust-lang/lang-team#160 (comment)

Tracking issues
- Lang, rust-lang#96373
- Libs-api, rust-lang#96374
workingjubilee pushed a commit to tcdi/postgrestd that referenced this issue Sep 15, 2022
Add `do yeet` expressions to allow experimentation in nightly

Two main goals for this:
- Ensure that trait restructuring in rust-lang/rust#84277 (comment) doesn't accidentally close us off from the possibility of doing this in future, as sketched in https://rust-lang.github.io/rfcs/3058-try-trait-v2.html#possibilities-for-yeet
- Experiment with the *existence* of syntax for this, to be able to weight the syntax-vs-library tradeoffs better than we can right now.  Notably the syntax (with `do`) and name in this PR are not intended as candidates for stabilization, but they make a good v0 PR for adding this with minimal impact to compiler maintenance or priming one possible name choice over another.

r? `@oli-obk`
The lang `second` for doing this: rust-lang/lang-team#160 (comment)

Tracking issues
- Lang, rust-lang/rust#96373
- Libs-api, rust-lang/rust#96374
@reza-ebrahimi
Copy link

return Err(...) vs do yeet.

What are exactly the added values here?

@andersk
Copy link
Contributor

andersk commented Nov 28, 2022

@reza-ebrahimi yeet has an implicit .into() conversion that return Err(…) lacks.

fn main() -> Result<(), Box<dyn std::error::Error>> {
    return Err("string message"); // compile error, wrong type
    return Err("string message".into()); // works
    Err("string message")?; // works
    do yeet "string message"; // works
}

It also works with Option, custom types (with feature(try_trait_v2_yeet)), and try blocks (with feature(try_blocks)).

I think Err(…)? is almost equivalent for functions returning Result, except there’s a little semicolon pitfall when the Ok type isn’t ().

fn err_semicolon() -> Result<bool, i32> {
    Err(1)?; // compile error, wrong type
}
fn err_no_semicolon() -> Result<bool, i32> {
    Err(1)? // works
}
fn yeet_semicolon() -> Result<bool, i32> {
    do yeet 1; // works
}
fn yeet_no_semicolon() -> Result<bool, i32> {
    do yeet 1 // works
}

@clarfonthey
Copy link
Contributor

clarfonthey commented Jan 1, 2023

Hmm, Err(1)?; not being valid strikes me as a bug, since it's conceptually equivalent to return Err(1.into()); and a diverging semicolon coerces to !, not ().

Edit: I filed #106357 to track this discussion.

@reza-ebrahimi
Copy link

@andersk Sorry for the late reply.

The thing I'm trying to understand is why we need to add a new keyword to language for these kinds of scenarios?

There should be many scenarios of this kind we can find in the Rust language and do we really need to resolve them by adding a new keyword to the language?

@Cypher1
Copy link

Cypher1 commented Jan 28, 2023

@reza-ebrahimi I've been thinking about why 'yeet' is different than other cases. I've tried to give a response here, but am happy to take this conversation elsewhere :)

A Yeet keyword (and functions that support yeet and return to produce Result values) is mitigation for the ergonomics problems of Ok wrapping and constructing errors.

In my experience this is near the top of the ergonomics issues that Rust faces (and it seems many others agree: this keyword has been proposed many times, under multiple names).
It will also slightly reduce the confusion of those coming from exception backed languages (i.e. most languages).

As one of the most tractable issues that Rust faces (as it's entirely syntactic sugar) I would love to see this get in!

Personally I'd prefer it was named throw to match other languages, despite the mismatch in implementations (i.e. there's no result/exception stack in Rust). I'd also like a ReturningInto or similar trait so that return can have a less easily misused version of Into just for returning values like Ok and Some but that may be overkill.

@jgarvin
Copy link

jgarvin commented Jan 28, 2023

I think the infix $ operator from Haskell would solve most of ergonomics problem yeet is targeting in a more general way. It would take a function or type constructor on the left hand side and an expression on the right, then feeds the expression into the function, so you could write return Err $ foo instead of return Err(foo). It's also right associative so you can do return Err $ foo $ bar instead of return Err(foo(bar)).

@Cypher1
Copy link

Cypher1 commented Jan 29, 2023

I think the infix $ operator from Haskell would solve most of ergonomics problem yeet is targeting in a more general way. It would take a function or type constructor on the left hand side and an expression on the right, then feeds the expression into the function, so you could write return Err $ foo instead of return Err(foo). It's also right associative so you can do return Err $ foo $ bar instead of return Err(foo(bar)).

That's a solution for a different problem imo, (and is often complained about by new Haskellers).

It only reduces the number of parens (while making it harder to parse (as a human) and requiring work arounds when multiple arguments come into play.

More importantly though, it doesn't provide a short hand for return Err(x).into() or avoid the need for Ok wrapping.

@withoutboats
Copy link
Contributor

withoutboats commented Jun 23, 2023

(NOT A CONTRIBUTION)

Not mentioned in this thread, but critical for understanding this feature, is that it would be intended to interact with try blocks, which would act as a boundary for ? and "yeet" expressions but not return expressions. The point is to create syntactic sugar for an "error handling" scope inside a function, and while there is already an operation for "forward an error from another function" (which is ?) there is no operation for "raise an error of your own."

Of course the same thing could be achieved with Err(expr)?, but this pattern is rather indirect and unclear. The point is to give users clearer structure.

e.g.

try {
    // Do some kind of fallible IO operation, and fail if it returns false
    if !do_io()? {
        do yeet DoIoFalseError;
    };

    // More code here...
}

@appetrosyan
Copy link

appetrosyan commented Aug 19, 2023

Of course the same thing could be achieved with Err(expr)?, but this pattern is rather indirect and unclear. The point is to give users clearer structure.

I would argue that while this pattern is somewhat clearer in zig, it is so because in zig there is no such thing as a macro. If the postfix syntax were stabilised, the same pattern could be achieved with

try {
  if !do_io? {
      DoIoFalseError.yeet!();
  };
}

Also for the record, the monadic error handling actually encourages a different approach:

try { do_io()?.failing(DoIoFalseError)? } 
// Or call it `map_false` if you like. 

where all one really needs is an extension trait and a tiny library in Cargo.toml if this is not included in the standard library (and frankly, I don't think it should be).

@dev-ardi
Copy link
Contributor

We're all fine with things like reborrowing. I don't see how Err(1)? is more indirect and unclear than &*string.

Rust is complex enough as is, the fewer symbols we need for basic things such as error handling that you learn early on the better.

@tedliosu
Copy link

tedliosu commented Mar 5, 2024

Of course the same thing could be achieved with Err(expr)?, but this pattern is rather indirect and unclear. The point is to give users clearer structure.

I would argue that while this pattern is somewhat clearer in zig, it is so because in zig there is no such thing as a macro. If the postfix syntax were stabilised, the same pattern could be achieved with

try {
  if !do_io? {
      DoIoFalseError.yeet!();
  };
}

Also for the record, the monadic error handling actually encourages a different approach:

try { do_io()?.failing(DoIoFalseError)? } 
// Or call it `map_false` if you like. 

where all one really needs is an extension trait and a tiny library in Cargo.toml if this is not included in the standard library (and frankly, I don't think it should be).

Complete newbie to Rust here, but I've done quite my fair share of system programming in C, and am also very tired of the lack of rigorously defined try...catch type structures in relatively simple lower level system languages (not that try...catch should be used pervasively anyway). So what if we did something like (notice the use of the more formal yield keyword):

try {
   if !do_io? {
       DoIoFalseError.yield!();
   };
}

Or perhaps the "monadic error handling" way as mentioned by @appetrosyan in this case would be?:

try { do_io()?.fails_over_to(DoIoFalseError)? } 
  // Or call it `map_false` if you like. 

Like @withoutboats said I wouldn't count my 2 cents as an official contribution, just that I personally believe we need to remind ourselves that just like natural languages, the way we name things in programming languages can take advantage of existing "synonyms" based on natural languages.

I understand reusing the same keyword from another very popular language (Python) in a different way might cause severe confusion for beginners, but unfortunately I am unable to think of a better alternative at the moment. :/

And to address @dev-ardi 's point: yes adding more keywords will complicate the language, but unfortunately as well the nature of such things like FILE I/O is that so many things could go wrong (lock on file un-acquireable, file is encoded in wrong format, etc. etc.) that having a relatively simple try...catch way of doing structured programming may actually be very beneficial for all in the long term. DO NOT get me started on having to include some random error.h header for every single darn possible system error known to humankind, lol.

Sources of inspiration:
Python yield keyword
Yield signs in real life

P.S. minutes after creating this comment, just saw this stack-overflow post and I gotta say that it seems to me using "yield" for iterators seems to be a rather terrible idea imho.

P.P.S. what if...synonyms for throw...?

try {
   if !do_io? {
       DoIoFalseError.lob!();
   };
}

@tedliosu
Copy link

Of course the same thing could be achieved with Err(expr)?, but this pattern is rather indirect and unclear. The point is to give users clearer structure.

I would argue that while this pattern is somewhat clearer in zig, it is so because in zig there is no such thing as a macro. If the postfix syntax were stabilised, the same pattern could be achieved with

try {
  if !do_io? {
      DoIoFalseError.yeet!();
  };
}

Also for the record, the monadic error handling actually encourages a different approach:

try { do_io()?.failing(DoIoFalseError)? } 
// Or call it `map_false` if you like. 

where all one really needs is an extension trait and a tiny library in Cargo.toml if this is not included in the standard library (and frankly, I don't think it should be).

Complete newbie to Rust here, but I've done quite my fair share of system programming in C, and am also very tired of the lack of rigorously defined try...catch type structures in relatively simple lower level system languages (not that try...catch should be used pervasively anyway). So what if we did something like (notice the use of the more formal yield keyword):

try {
   if !do_io? {
       DoIoFalseError.yield!();
   };
}

Or perhaps the "monadic error handling" way as mentioned by @appetrosyan in this case would be?:

try { do_io()?.fails_over_to(DoIoFalseError)? } 
  // Or call it `map_false` if you like. 

Like @withoutboats said I wouldn't count my 2 cents as an official contribution, just that I personally believe we need to remind ourselves that just like natural languages, the way we name things in programming languages can take advantage of existing "synonyms" based on natural languages.

I understand reusing the same keyword from another very popular language (Python) in a different way might cause severe confusion for beginners, but unfortunately I am unable to think of a better alternative at the moment. :/

And to address @dev-ardi 's point: yes adding more keywords will complicate the language, but unfortunately as well the nature of such things like FILE I/O is that so many things could go wrong (lock on file un-acquireable, file is encoded in wrong format, etc. etc.) that having a relatively simple try...catch way of doing structured programming may actually be very beneficial for all in the long term. DO NOT get me started on having to include some random error.h header for every single darn possible system error known to humankind, lol.

Sources of inspiration: Python yield keyword Yield signs in real life

P.S. minutes after creating this comment, just saw this stack-overflow post and I gotta say that it seems to me using "yield" for iterators seems to be a rather terrible idea imho.

P.P.S. what if...synonyms for throw...?

try {
   if !do_io? {
       DoIoFalseError.lob!();
   };
}

OK, I have to apologize here as I have clearly overstepped my bounds here a bit, esp. as I realized after some more thinking that having any extra keywords that needs to be memorized for Rust will in fact likely make the language a bit too complicated especially for beginners, and to be fair half the time I just make up stuff that I end up having to retract later.

HOWEVER, the only reason why I'm not jumping onto the Rust train right now is that it is still not as mature as, say, C++, especially given how much of the software world is dependent on code written in C++ (e.g. .NET, Qt, JVM, etc. etc.), and I believe as Theo from t3.gg especially rightly said during this twitch stream (I sometimes misremember so please forgive me if I mis-paraphrase), it's not always a wise idea to just jump onto the latest new tech for any given project. Theo's argument for not doing that is more related to the idea of maintaining a flexible software stack, but I'm borrowing his argument here (esp. as someone who's dabbled with the likes of CUDA, ROCm, OpenMP, etc.) especially for software used as infrastructure (e.g. the Linux Kernel, Oracle Virtualbox), since rely on something brand new for something that cannot be changed at a moment's notice is never a good idea. Basically, with software used as infrastructure one has to take a lot of precautions around the idea of "moving fast", as "moving too fast" can cause critical system breakage.

NONETHELESS, I don't want to have to dismiss Rust as a viable systems programming candidate, as I really like how its "mutable borrow" philosophy encourages the writing of safe code, but I understand the sentiment behind keeping things as simple as possible. BUT I also hate to see progress on Rust being stalled due to unsettled disagreements about fundamental keywords that may be a necessary part of the programming language. So my question ultimately is:

To solve the issue of having a better version of something like setjmp.h in C due to the lack of try...catch like structures in C (given how Rust is supposed to be a "long term successor" to languages like C++ AND C, correct me if I'm wrong), would it be ultimately better in the long run for both the implementation of Rust AND those who write code using Rust to have something like try...catch, OR to simply have a "setjmp.h" like library which may help to take care of things like exception handling?

Unfortunately my very limited experience in designing programming languages doesn't allow me to participate in this discussion in a truly meaningful way, but again I'm just trying to encourage more discussion around this topic in the hopes that this issue won't be a cause for stalling of the development of Rust itself.

@dev-ardi
Copy link
Contributor

I'm just trying to encourage more discussion around this topic in the hopes that this issue won't be a cause for stalling of the development of Rust itself.

I've read your comments like 3 times and I'm still not sure what point you're trying to raise honestly 😄

@tedliosu
Copy link

I'm just trying to encourage more discussion around this topic in the hopes that this issue won't be a cause for stalling of the development of Rust itself.

I've read your comments like 3 times and I'm still not sure what point you're trying to raise honestly 😄

My point is: do we want to keep discussing this yeet-based issue for the next two years again, or is it time to find a solution and move on for productivity's sake? Does that make sense? Hope I'm not being too harsh with this.

@ghost
Copy link

ghost commented Jul 2, 2024

The argument that yeet would add more complexity makes no sense. If yeet is too complex for you then just don't use it.

I find return Err(...), etc very annoying and I think a new keyword is definitely worth it.

Naming-wise a have a concern: why is it do yeet and not just yeet? Also, I saw a suggestion for throw and I think it would be inappropriate because we throw exceptions and the equivalent of exceptions in Rust are panics.

@dev-ardi
Copy link
Contributor

dev-ardi commented Jul 3, 2024

why is it do yeet and not just yeet?

do is a reserved keyword already and since yeet is definitely not gonna be the final name it makes no sense to reseve it.

@ghost
Copy link

ghost commented Jul 3, 2024

why is it do yeet and not just yeet?

do is a reserved keyword already and since yeet is definitely not gonna be the final name it makes no sense to reseve it.

What’s blocking us from using yeet as the final name?

@appetrosyan
Copy link

Common sense? You wouldn’t call objects thangs.

@ghost
Copy link

ghost commented Jul 3, 2024

Common sense? You wouldn’t call objects thangs.

I assume you’re implying that thang to object is like yeet to throw. I agree that I would not call throw yeet but this feature is not throw so i would not be calling throw yeet, I would be calling this unnamed feature.

@appetrosyan
Copy link

I assume you’re implying that thang to object is like yeet to throw. I agree that I would not call throw yeet but this feature is not throw so i would not be calling throw yeet, I would be calling this unnamed feature.

If we're totally honest, the try block is not the try of Python, so there's no reason why you couldn't call it throw. It might confuse some people, but to be fair, C++ programmers can wrap their head around & as in borrow vs. reference.

Secondly, eject or raise or even hurl could work. Hurl has the advantage of me being able to type it on a steno keyboard.

@ghost
Copy link

ghost commented Jul 3, 2024

If we're totally honest, the try block is not the try of Python, so there's no reason why you couldn't call it throw

Just because I can call something something doesn’t mean I will. Indeed, we could call the try block the throw block but how appropriate would that be?

@appetrosyan
Copy link

Just because I can call something something doesn’t mean I will. Indeed, we could call the try block the throw block but how appropriate would that be?

I don't see the point in arguing. Just give me a lint to catch these expressions, so I can clippy --fix them away.

@tkaitchuck
Copy link
Contributor

The argument that yeet would add more complexity makes no sense. If yeet is too complex for you then just don't use it.

That's the thing, you can't not teach it to users. It adds yet another chapter to the Rust book which everyone needs to read and understand to be able to read Rust code. From the perspective of a new user:

Err(...)?

Is surprising the first time you see it. But it can be parsed without learning any new concepts. Once you've seen it, it's easy to remember, and isn't even any more typing. So adding yeet increases the barrier to adoption, without any advantage besides subjective aesthetics.

@ghost
Copy link

ghost commented Jul 22, 2024

So adding yeet increases the barrier to adoption, without any advantage besides subjective aesthetics.

The advantage is a significant boilerplate reduction.

@dev-ardi
Copy link
Contributor

A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.

Please find another place to have the discussion like https://internals.rust-lang.org or zulip.

@jieyouxu jieyouxu added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Jul 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests