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 `?` operator and `try` blocks (RFC 243, `question_mark` & `try_blocks` features) #31436

Open
nikomatsakis opened this Issue Feb 5, 2016 · 234 comments

Comments

Projects
None yet
@nikomatsakis
Copy link
Contributor

nikomatsakis commented Feb 5, 2016

Tracking issue for rust-lang/rfcs#243 and rust-lang/rfcs#1859.

Implementation concerns:

  • ? operator that is roughly equivalent to try! - #31954
  • catch { ... } expression - #39849
    • resolve do catch { ... } syntax question
      • Resolved as try { .. }, - #50412
    • resolve whether catch blocks should "wrap" result value (#41414)
  • settle design of the Try trait (rust-lang/rfcs#1859)
    • implement new Try trait (in place of Carrier) and convert ? to use it (#42275)
    • add impls for Option and so forth, and a suitable family of tests (#42526)
    • improve error messages as described in the RFC (#35946)
  • reserve try in new edition
  • block try{}catch (or other following idents) to leave design space open for the future, and point people to how to do what they want with match instead
@reem

This comment has been minimized.

Copy link
Contributor

reem commented Feb 5, 2016

The accompanying RFC discusses a desugaring based on labeled return/break, are we getting that too or will there just be special treatment for ? and catch in the compiler?

EDIT: I think labeled return/break is an excellent idea separate from ? and catch, so if the answer is no I will probably open a separate RFC for it.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 5, 2016

Labeled return/break is purely for explanatory purposes.

On Fri, Feb 5, 2016 at 3:56 PM, Jonathan Reem notifications@github.com
wrote:

The accompanying RFC discusses a desugaring based on labeled return/break,
are we getting that too or will there just be special treatment for ? and
catch in the compiler?


Reply to this email directly or view it on GitHub
#31436 (comment).

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented Feb 5, 2016

Another unresolved question we have to resolve before stabilizing is what the contract which impls of Into have to obey should be -- or whether Into is even the right trait to use for the error-upcasting here. (Perhaps this should be another checklist item?)

@petrochenkov

This comment has been minimized.

Copy link
Contributor

petrochenkov commented Feb 5, 2016

@reem

I think labeled return/break is an excellent idea ... I will probably open a separate RFC for it.

Please do!

@thepowersgang

This comment has been minimized.

Copy link
Contributor

thepowersgang commented Feb 6, 2016

On the subject of the Carrier trait, here is a gist example of such a trait I wrote back early in the RFC process.
https://gist.github.com/thepowersgang/f0de63db1746114266d3

@petrochenkov

This comment has been minimized.

Copy link
Contributor

petrochenkov commented Feb 6, 2016

How this is treated during parsing?

struct catch {
    a: u8
}

fn main() {

    let b = 10;
    catch { a: b } // struct literal or catch expression with type ascription inside?

}
@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Feb 6, 2016

@petrochenkov Well, the definition couldn't affect parsing, but I think we still have a lookahead rule, based on the second token after {, : in this case, so it should still be parsed as a struct literal.

@petrochenkov

This comment has been minimized.

Copy link
Contributor

petrochenkov commented Feb 6, 2016

Also

let c1 = catch { a: 10 };
let c2 = catch { ..c1 }; // <--

struct catch {}
let c3 = catch {}; // <--

+ rust-lang/rfcs#306 if (when!) implemented.
It seems like there are no conflicts besides struct literals.

Given the examples above I'm for the simplest solution (as usual) - always treat catch { in expression positions as start of a catch block. Nobody calls their structures catch anyway.

@nikomatsakis nikomatsakis removed the A-const-fn label Feb 6, 2016

@durka

This comment has been minimized.

Copy link
Contributor

durka commented Feb 6, 2016

It would be easier if a keyword was used instead of catch.

@bluss

This comment has been minimized.

Copy link
Contributor

bluss commented Feb 6, 2016

@durka

This comment has been minimized.

Copy link
Contributor

durka commented Feb 6, 2016

@bluss yeah, I admit none of them are great... override seems like the only one that is close. Or we could use do, heh. Or a combination, though I don't see any great ones immediately. do catch?

@bluss

This comment has been minimized.

Copy link
Contributor

bluss commented Feb 6, 2016

do is the only one that seems to be close IMO. A keyword soup with do as prefix is a bit irregular, not similar to any other part of the language? Is while let a keyword soup as well? That one feels ok now, when you are used to it.

@est31

This comment has been minimized.

Copy link
Contributor

est31 commented Feb 7, 2016

port try! to use ?

Can't ? be ported to use try! instead? This would allow for the use case where you want to get a Result return path, e.g. when debugging. With try! this is fairly easy, you just override the macro at the beginning of the file (or in lib/main.rs):

macro_rules! try {
    ($expr:expr) => (match $expr {
        Result::Ok(val) => val,
        Result::Err(err) => {
            panic!("Error occured: {:?}", err)
        }
    })
}

You will get a panic stack trace starting from the first occurrence of try! in the Result return path. In fact, if you do try!(Err(sth)) if you discover an error instead of return Err(sth), you even get the full stack trace.

But when debugging foreign libraries written by people who haven't implemented that trick, one relies on try! usage somewhere higher in the chain. And now, if the library uses the ? operator with hardcoded behaviour, getting a stacktrace gets almost impossible.

It would be cool if overriding try! would affect the ? operator as well.

Later on when the macro system gets more features you can even panic! only for specific types.

If this proposal requires an RFC please let me know.

@rpjohnst

This comment has been minimized.

Copy link
Contributor

rpjohnst commented Feb 7, 2016

Ideally ? could just be extended to provide stack trace support directly, rather than relying on the ability to override try!. Then it would work everywhere.

@durka

This comment has been minimized.

Copy link
Contributor

durka commented Feb 7, 2016

Stack traces are just one example (though a very useful one, seems to me).
If the Carrier trait is made to work, maybe that can cover such extensions?

On Sun, Feb 7, 2016 at 4:14 PM, Russell Johnston notifications@github.com
wrote:

Ideally ? could just be extended to provide stack trace support directly,
rather than relying on the ability to override try!. Then it would work
everywhere.


Reply to this email directly or view it on GitHub
#31436 (comment).

@est31

This comment has been minimized.

Copy link
Contributor

est31 commented Feb 8, 2016

Without wanting to speculate, I think that it could work, albeit with some issues.

Consider the usual case where one has code returning some Result<V,E> value. Now we would need to allow for multiple implementations of the Carrier trait to coexist. In order to not run into E0119, one has to make all implementations out of scope (possibly through different traits which are per default not imported), and when using the ? operator, the user is required to import the wished implementation.

This would require everybody, even those who don't want to debug, to import their wished trait implementation when using ?, there would be no option for a predefined default.

Possibly E0117 can be an issue too if wanting to do custom Carrier implementations for Result<V,E>, where all types are outside bounds, so libstd should provide already provide a set of implementations of the Carrier trait with the most used use cases (trivial implementation, and panic! ing implementation, perhaps more).

Having the possibility to override via a macro would provide a greater flexibility without the additional burden on the original implementor (they don't have to import their wished implementation). But I also see that rust never had a macro based operator before, and that implementing ? via a macro isn't possible if catch { ... } is supposed to work, at least not without additional language items (return_to_catch, throw, labeled break with param as used in RFC 243).

I am okay with any setup which enables one to get Result stacktraces with an Err return path, while having only to modify a very small amount of code, prefferably at the top of the file. The solution should also work unrelated to how and where the Err type is implemented.

@rphmeier

This comment has been minimized.

Copy link
Contributor

rphmeier commented Feb 8, 2016

Just to chime in on bikeshedding: catch in { ... } flows pretty nicely.

@durka

This comment has been minimized.

Copy link
Contributor

durka commented Feb 8, 2016

catch! { ... } is another backcompat choice.

@durka

This comment has been minimized.

Copy link
Contributor

durka commented Feb 8, 2016

Also, not that I expect this to change anything, but a note that this is going to break multi-arm macros that were accepting $i:ident ?, in the same way that type ascription broke $i:ident : $t:ty.

@dgrunwald

This comment has been minimized.

Copy link
Contributor

dgrunwald commented Feb 8, 2016

Don't overdo the backwards compatibility, just treat catch as a keyword when followed by { (possibly only in expression position, but I'm not sure if that changes much compatibility-wise).

I can also imagine some possible problems that don't involve struct literals (e.g. let catch = true; if catch {}); but I prefer a minor breaking change over a more ugly syntax.

Didn't we have a for adding new keywords, anyways? We could offer some kind of from __future__ opt-in for new syntax; or specify a rust language version number on the command-line / in Cargo.toml.
I highly doubt that in the long term, we'll be able to work with only those keywords that are already reserved. We don't want our keywords to have three different meanings each, depending on context.

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented Feb 9, 2016

I agree. This isn't even the first RFC where this has come up (rust-lang/rfcs#1444 is another example). I expect it won't be the last. (Also default from rust-lang/rfcs#1210, although it's not an RFC I'm in favor of.) I think we need to find a way to add honest-to-god keywords instead of trying to figure out how to ad-hoc hack the grammar for every new case.

@rkjnsn

This comment has been minimized.

Copy link
Contributor

rkjnsn commented Feb 26, 2016

Wasn't the whole argument for not reserving several keywords prior to 1.0 that we'd definitely be introducing a way to add new keywords to the language backward compatibly (possibly by explicitly opting in), so there was no point? Seems like now would be a good time.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Feb 26, 2016

@japaric Are you interested in reviving your old PR and taking this on?

@japaric

This comment has been minimized.

Copy link
Member

japaric commented Feb 26, 2016

@aturon My implementation simply desugared foo? in the same way as try!(foo). It also only worked on method and function calls, i.e. foo.bar()? and baz()? work but quux? and (quux)? don't. Would that be okay for an initial implementation?

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Feb 26, 2016

@japaric What was the reason for restricting it to methods and function calls? Wouldn't parsing it be easier as a general postfix operator?

bors added a commit that referenced this issue Jul 28, 2018

Auto merge of #52602 - scottmcm:tryblock-expr, r=nikomatsakis
Implement try block expressions

I noticed that `try` wasn't a keyword yet in Rust 2018, so...

Fixes #52604
cc #31436 #50412

@ehuss ehuss referenced this issue Aug 21, 2018

Open

Syntax Highlighting: Unstable Tracking Issue #333

2 of 7 tasks complete

Mark-Simulacrum added a commit to Mark-Simulacrum/rust that referenced this issue Aug 22, 2018

Rollup merge of rust-lang#52602 - scottmcm:tryblock-expr, r=nikomatsakis
Implement try block expressions

I noticed that `try` wasn't a keyword yet in Rust 2018, so...

~~Fix​es rust-lang#52604 That was fixed by PR rust-lang#53135
cc rust-lang#31436 rust-lang#50412

bors added a commit that referenced this issue Aug 23, 2018

Auto merge of #52602 - scottmcm:tryblock-expr, r=nikomatsakis
Implement try block expressions

I noticed that `try` wasn't a keyword yet in Rust 2018, so...

~~Fix​es #52604 That was fixed by PR #53135
cc #31436 #50412

bors added a commit that referenced this issue Aug 23, 2018

Auto merge of #52602 - scottmcm:tryblock-expr, r=nikomatsakis
Implement try block expressions

I noticed that `try` wasn't a keyword yet in Rust 2018, so...

~~Fix​es #52604 That was fixed by PR #53135
cc #31436 #50412

bors added a commit that referenced this issue Aug 23, 2018

Auto merge of #52602 - scottmcm:tryblock-expr, r=nikomatsakis
Implement try block expressions

I noticed that `try` wasn't a keyword yet in Rust 2018, so...

~~Fix​es #52604 That was fixed by PR #53135
cc #31436 #50412

@scottmcm scottmcm changed the title Tracking issue for `?` operator and `catch` expressions (RFC 243, `question_mark` feature) Tracking issue for `?` operator and `try` blocks (RFC 243, `question_mark` & `try_blocks` features) Sep 30, 2018

@earthengine

This comment has been minimized.

Copy link

earthengine commented Oct 3, 2018

As all check boxes are ticked in the top post, when would us move forward? If there are still unresolved issues we need to have new check box(es) added.

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Oct 3, 2018

There are probably plenty of remaining unresolved questions not recorded.
For example, the ok-wrapping behavior is not settled within the lang team, the design of the Try is not finalized, and so on. We should probably split this issue up into several more targeted ones as it has likely outlived it's usefulness.

@mark-i-m

This comment has been minimized.

Copy link
Contributor

mark-i-m commented Oct 3, 2018

Hmm... it bothers me that these questions haven't been recorded.

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Oct 3, 2018

@mark-i-m So to clarify, I think they have been somewhere; but not in one location; it's a bit scattered atm in various RFCs and issues and such, so what we need to do is record them in the proper locations.

@scottmcm

This comment has been minimized.

Copy link
Member

scottmcm commented Oct 3, 2018

The design of the backing trait is tracked in #42327; there's extensive discussion there about weaknesses in the current one and a possible new direction. (I'm planning of making a pre-RFC for a change there once 2018 settles a bit.)

So I think only try{} is left here, and the only disagreement I know of for that are things that were settled in the RFC and re-confirmed in one of the above-mentioned issues. It could still be good to have a distinct tracking issue, though.

I'll add a checkbox for the one pending implementation task I know still needs to be done...

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Oct 3, 2018

@scottmcm I know @joshtriplett had concerns about OK-wrapping (noted in the try RFC) and I'd personally like to restrict break in the initial stabilization of try { .. } so that you can't do loop { try { break } } and such.

@earthengine

This comment has been minimized.

Copy link

earthengine commented Oct 3, 2018

@Centril

so that you can't do loop { try { break } }

Right now, you cannot use break in a non-loop block, and it is correct: break should only be used in loops. To early leave a try block, the standard way is to write Err(e)?. and it forces that early leaves are always in the "abnormal" control path.

So my proposal is the code you shown should be allowed, and it should break the loop, not just leaving the try.

The immediate benefit, is when you see break you know it is going break from a loop, and you can always replace it with a continue. Also, it removes the need to having to label the break point when using try blocks inside a loop and you want to exit the loop.

@joshtriplett

This comment has been minimized.

Copy link
Member

joshtriplett commented Oct 4, 2018

@Centril Thank you for raising those.

Regarding break, I personally would be fine with simply saying that try doesn't care about break and it passes through to the containing loop. I just don't want break to interact with try at all.

As for Ok-wrapping, yes, I'd like to address that before stabilizing try.

@scottmcm

This comment has been minimized.

Copy link
Member

scottmcm commented Oct 5, 2018

@Centril Yes, I'm aware. But it's important to remember that that's re-re- raising the issue. The RFC decided to have it, it was implemented without it, but then the original intent was taken again, and the implementation changed to follow the RFC. So my big question is whether any material facts have changed, especially given that this is one of the noisiest topics I've ever seen discussed on RFCs+IRLO.

bmisiak added a commit to bmisiak/capnproto-rust that referenced this issue Oct 6, 2018

Switch from try!() to ? in generated code to make it work in Rust 2018
Rust 2018 removes the `try!()` macro and makes `try` a reserved keyword in accordance with RFC 2388: rust-lang/rfcs#2388. On the other hand, the `?` operator was introduced way back in Rust 1.13 and is now stable.

Without the change, rustc will refuse to compile the generated code starting with the 2018 edition. While this project itself is free to use `try!()` and not switch to Rust 2018, its output files - preferably - should be compatible with both editions.

rust-lang/rust#31436

carols10cents added a commit to rust-lang/book that referenced this issue Oct 23, 2018

Add keywords that are reserved for 2018's future use.
`async` was added in rust-lang/rust#50307 and is
not yet implemented

`try` was added in rust-lang/rust#52602 and is
not yet stable: rust-lang/rust#31436
@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Nov 8, 2018

@scottmcm Of course, as you know, I agree with retaining Ok-wrapping ;) and I agree that the issue should be considered settled.

@chrysn chrysn referenced this issue Nov 16, 2018

Open

no_std support #79

@fschutt

This comment has been minimized.

Copy link
Contributor

fschutt commented Nov 25, 2018

I just wanted to comment on this, not sure if this is the right thing:

Essentially, a situation I have is callbacks in a GUI framework - instead of returning an Option or Result, they need to return a UpdateScreen, to tell the framework if the screen needs to be updated or not. Often I don't need logging at all (it's simply not practical to log on every minor error) and simply return a UpdateScreen::DontRedraw when an error has occurred. However, with the current ? operator, I have to write this all the time:

let thing = match fs::read(path) {
    Ok(o) => o,
    Err(_) => return UpdateScreen::DontRedraw,
};

Since I can't convert from a Result::Err into a UpdateScreen::DontRedraw via the Try operator, this gets very tedious - often I have simple lookups in hash maps that can fail (which isn't an error) - so often in one callback I have 5 - 10 usages of the ? operator. Because the above is very verbose to write, my current solution is to impl From<Result<T>> for UpdateScreen like this, and then use an inner function in the callback like this:

fn callback(data: &mut State) -> UpdateScreen {
     fn callback_inner(data: &mut State) -> Option<()> {
         let file_contents = fs::read_to_string(data.path).ok()?;
         data.loaded_file = Some(file_contents);
         Some(())
     }
    
    callback_inner(data).into()
}

Since the callback is used as a function pointer, I can't use an -> impl Into<UpdateScreen> (for some reason, returning an impl is currently not allowed for function pointers). So the only way for me to use the Try operator at all is to do the inner-function trick. It would be nice if I could simply do something like this:

impl<T> Try<Result<T>> for UpdateScreen {
    fn try(original: Result<T>) -> Try<T, UpdateScreen> {
        match original {
             Ok(o) => Try::DontReturn(o),
             Err(_) => Try::Return(UpdateScreen::DontRedraw),
        }
    }
}

fn callback(data: &mut State) -> UpdateScreen {
     // On any Result::Err, convert to an UpdateScreeen::DontRedraw and return
     let file_contents = fs::read_to_string(data.path)?;
     data.loaded_file = Some(file_contents);
     UpdateScreen::Redraw
}

I am not sure if this would be possible with the current proposal and just wanted to add my use-case for consideration. It would be great if a custom Try operator could support something like this.

@KrishnaSannasi

This comment was marked as resolved.

Copy link
Contributor

KrishnaSannasi commented Dec 12, 2018

EDIT:
I made a mistake.


Ignore this post


Could this play better with type inference, it fails even in simple cases.

fn test_try(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };
    
    let x // : Option<_> // why is this type annotation necessary
    = try { div? + 1 };
    
    println!("{:?}", x);
}

If this is re-written to use a closure instead of the try block (and in the process loose auto wrapping), then we get

fn test_closure(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };
    
    let x =  (|| (div? + 1).into())();
    
    println!("{:?}", x);
}

Which doesn't require a type annotation, but it does require that we wrap the result.

playground

@Nemo157

This comment was marked as resolved.

Copy link
Contributor

Nemo157 commented Dec 13, 2018

@KrishnaSannasi your closure based example has a type inference failure as well (playground) because Into doesn't constrain the output and you don't use it anywhere that does later.

This seems to mostly be an issue with the Try trait rather than try blocks, similar to Into it doesn't propagate any type information from the inputs to the output, so the output type must be determinable by its later usage. There's a lot of discussion in #42327 about the trait, I haven't read it so I'm not sure if any of the proposals there could fix this issue.

@KrishnaSannasi

This comment was marked as resolved.

Copy link
Contributor

KrishnaSannasi commented Dec 13, 2018

@Nemo157

Yes, I did a last minute change to my code, to make it use into, and didn't test that. My bad.

djrenren pushed a commit to djrenren/libtest that referenced this issue Jan 22, 2019

Stabilise `?`
cc [`?` tracking issue](rust-lang/rust#31436)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment