Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upTracking issue for `ops::Try` (`try_trait` feature) #42327
Comments
apasel422
added
B-RFC-implemented
B-unstable
labels
May 31, 2017
This comment has been minimized.
This comment has been minimized.
|
A couple of pieces of bikeshedding:
(updated version of playground link from the RFC) |
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
fluffysquirrels
commented
Jun 30, 2017
|
I'm not sure if this is the correct forum for this comment, please redirect me if it's not I was wondering if there is a case for splitting up the
Following the match expr { // Removed `Try::into_result()` here.
Ok(v) => v,
Err(e) => return Try::from_error(From::from(e)),
}The motivating examples I considered are FutureWe can implement impl<T, E> FromResult for FutureResult {
type Ok = T;
type Error = E;
fn from_error(v: Self::Error) -> Self {
future::err(v)
}
fn from_ok(v: Self::Ok) -> Self {
future::ok(v)
}
}Assuming a reasonable implementation for fn async_stuff() -> impl Future<V, E> {
let t = fetch_t();
t.and_then(|t_val| {
let u: Result<U, E> = calc(t_val);
async_2(u?)
})
}
fn fetch_t() -> impl Future<T, E> {}
fn calc(t: T) -> Result<U,E> {}That is exactly what I was trying to implement earlier today and Option
Hope that helps! |
Mark-Simulacrum
added
the
C-tracking-issue
label
Jul 22, 2017
This comment has been minimized.
This comment has been minimized.
|
I'm maybe very late to all this, but it occured to me this weekend that fn fun() -> SearchResult<Socks> {
search_drawer()?;
search_wardrobe()
}But in this case, the naming of the It would widen the meaning of the |
This comment has been minimized.
This comment has been minimized.
|
In prototyping a From there I also wished for a |
This comment has been minimized.
This comment has been minimized.
fluffysquirrels
commented
Oct 28, 2017
•
|
The |
This comment has been minimized.
This comment has been minimized.
ErichDonGubler
commented
Dec 29, 2017
|
Since PR #42275 has been merged, does that mean this issue has been resolved? |
This comment has been minimized.
This comment has been minimized.
|
@skade Note that a type can define whichever as the short-circuiting one, like @cuviper The version I ended up needing was either the @ErichDonGubler This is a tracking issue, so isn't resolved until the corresponding code is in stable. |
This comment has been minimized.
This comment has been minimized.
|
Experience report: I've been a little frustrated trying to put this trait into practice. Several times now I've been tempted to define my own variant on Example: In the new solver for the Chalk VM, I wanted to have an enum that indicates the result of solving a "strand". This had four possibilities: enum StrandFail<T> {
Success(T),
NoSolution,
QuantumExceeded,
Cycle(Strand, Minimums),
}I wanted enum StrandFail {
NoSolution,
QuantumExceeded,
Cycle(Strand, Minimums),
}But once I've got this type, then I might as well make type StrandResult<T> = Result<T, StrandFail>;and this is what I did. Now, this doesn't necessarily seem worse than having a single enum -- but it does feel a bit odd. Usually when I write the documentation for a function, for example, I don't "group" the results by "ok" and "error", but rather talk about the various possibilities as "equals". For example:
Note that I didn't say "we return (On the other hand, the current definition would help readers to know what the behavior of I guess this outcome is not that surprising: the current I'd also note that, in the case of |
This comment has been minimized.
This comment has been minimized.
|
Is the associated |
This comment has been minimized.
This comment has been minimized.
kevincox
commented
Feb 20, 2018
•
|
I don't follow either. I have successfully implemented Try for a type that had an internal error representation and it felt natural having In fact is seems like there is a fairly simple pattern for making this work. impl std::ops::Try for Value {
type Ok = Self;
type Error = Self;
fn from_ok(v: Self::Ok) -> Self { v }
fn from_error(v: Self::Error) -> Self { v }
fn into_result(self) -> Result<Self::Ok, Self::Error> {
if self.is_ok() { Ok(val) } else { Err(val) }
}
}If you want to unwrap the success something like this should work: impl std::ops::Try for StrandFail<T> {
type Ok = T;
type Error = Self;
fn from_ok(v: Self::Ok) -> Self { StrandFail::Success(v) }
fn from_error(v: Self::Error) -> Self { v }
fn into_result(self) -> Result<Self::Ok, Self::Error> {
match self {
StrandFail::Success(v) => Ok(v),
other => Err(other),
}
}
} |
This comment has been minimized.
This comment has been minimized.
|
Defining a structural type is possible but feels pretty annoying. I agree I could use |
This comment has been minimized.
This comment has been minimized.
|
Great experience report, @nikomatsakis! I've also been dissatisfied with TL/DR: I think Longer: I keep using
So if nothing else, I think the description I wrote for The other thing I've been thinking about is that we should consider some slightly-crazy impls for fn cmp(&self, other: &self) -> Ordering try {
self.a.cmp(&other.a)?;
self.b.cmp(&other.b)?;
}I don't yet know whether that's the good kind of crazy or the bad kind I do also note that that's another instance where the "error-conversion" isn't helpful. I also never used it in the "I want ? but it's not an error" examples above. And it's certainly known to cause inference sadness, so I wonder if it's a thing that should get limited to just Result (or potentially removed in favour of Edit: Oh, and all that makes me wonder if perhaps the answer to "I keep using Result for my errors instead of implementing Edit 2: This is not unlike #42327 (comment) above Edit 3: Seems like a |
This comment has been minimized.
This comment has been minimized.
|
My two cents: I like @fluffysquirrels's suggestion of splitting the trait into two traits. One for converting to a result, and another to convert from a result. But I do think we should keep the I also like @scottmcm's idea of using names that suggest break/continue rather than error/ok. |
This comment has been minimized.
This comment has been minimized.
|
To put the specific code in here, I like how this reads: rust/src/libcore/iter/iterator.rs Lines 1738 to 1746 in ab8b961 It's of course not quite as nice as a straight loop, but with "try methods" it would be close: self.try_for_each(move |x| try {
if predicate(&x) { return LoopState::Break(x) }
}).break_value() For comparison, I find the "error vocabulary" version really misleading: self.try_for_each(move |x| {
if predicate(&x) { Err(x) }
else { Ok(()) }
}).err() |
This comment has been minimized.
This comment has been minimized.
cowang4
commented
Apr 3, 2018
|
Can we implement Display for NoneError? It would allow the failure crate to automatically derive |
This comment has been minimized.
This comment has been minimized.
|
@cowang4 I'd like to try to avoid enabling any more mixing of Result-and-Option right now, as the type is unstable mostly to keep our options open there. I wouldn't be surprised if we ended up changing the design of |
This comment has been minimized.
This comment has been minimized.
cowang4
commented
Apr 3, 2018
|
@scottmcm Okay. I see your point. I'd eventually like a clean way to sugar the pattern of returning Err when an library function returns None. Maybe you know of one other than fn work_with_optional_types(pb: &PathBuf) -> Result<MyStruct, Error> {
if let Some(filestem) = pb.file_stem() {
if let Some(filestr) = filestem.to_str() {
return Ok(MyStruct {
filename: filestr.to_string()
});
}
}
Err(_)
}Once I found this experimental feature and the use failure::Error;
fn work_with_optional_types(pb: &PathBuf) -> Result<MyStruct, Error> {
Ok({
title: pb.file_stem?.to_str()?.to_string()
})
}Which almost works, except for the lack of a if option.is_none() {
return Err(_);
} |
This comment has been minimized.
This comment has been minimized.
|
@cowang4 I believe that would work if you implemented However, it is probably better practice to use |
This comment has been minimized.
This comment has been minimized.
cowang4
commented
Apr 3, 2018
|
@tmccombs I tried creating my own error type, but I must've done it wrong. It was like this: #[macro_use] extern crate failure_derive;
#[derive(Fail, Debug)]
#[fail(display = "An error occurred.")]
struct SiteError;
impl From<std::option::NoneError> for SiteError {
fn from(_err: std::option::NoneError) -> Self {
SiteError
}
}
fn build_piece(cur_dir: &PathBuf, piece: &PathBuf) -> Result<Piece, SiteError> {
let title: String = piece
.file_stem()?
.to_str()?
.to_string();
Ok(Piece {
title: title,
url: piece
.strip_prefix(cur_dir)?
.to_str()
.ok_or(err_msg("tostr"))?
.to_string(),
})
}
And then I tried using my error type... error[E0277]: the trait bound `SiteError: std::convert::From<std::path::StripPrefixError>` is not satisfied
--> src/main.rs:195:14
|
195 | url: piece
| ______________^
196 | | .strip_prefix(cur_dir)?
| |___________________________________^ the trait `std::convert::From<std::path::StripPrefixError>` is not implemented for `SiteError`
|
= help: the following implementations were found:
<SiteError as std::convert::From<std::option::NoneError>>
= note: required by `std::convert::From::from`
error[E0277]: the trait bound `SiteError: std::convert::From<failure::Error>` is not satisfied
--> src/main.rs:195:14
|
195 | url: piece
| ______________^
196 | | .strip_prefix(cur_dir)?
197 | | .to_str()
198 | | .ok_or(err_msg("tostr"))?
| |_____________________________________^ the trait `std::convert::From<failure::Error>` is not implemented for `SiteError`
|
= help: the following implementations were found:
<SiteError as std::convert::From<std::option::NoneError>>
= note: required by `std::convert::From::from`Okay, I just realized that it wants to know how to convert from other error types to my error, which I can write generically: impl<E: failure::Fail> From<E> for SiteError {
fn from(_err: E) -> Self {
SiteError
}
}Nope... error[E0119]: conflicting implementations of trait `std::convert::From<SiteError>` for type `SiteError`:
--> src/main.rs:183:1
|
183 | impl<E: failure::Fail> From<E> for SiteError {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T> std::convert::From<T> for T;Okay, what about impl<E: std::error::Error> From<E> for SiteError {
fn from(_err: E) -> Self {
SiteError
}
}That doesn't work either. Partly because it conflicts with my error[E0119]: conflicting implementations of trait `std::convert::From<std::option::NoneError>` for type `SiteError`:
--> src/main.rs:181:1
|
175 | impl From<std::option::NoneError> for SiteError {
| ----------------------------------------------- first implementation here
...
181 | impl<E: std::error::Error> From<E> for SiteError {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `SiteError`
|
= note: upstream crates may add new impl of trait `std::error::Error` for type `std::option::NoneError` in future versionsWhich is weird because I thought that error[E0277]: the trait bound `std::option::NoneError: std::error::Error` is not satisfied
--> src/main.rs:189:25
|
189 | let title: String = piece
| _________________________^
190 | | .file_stem()?
| |_____________________^ the trait `std::error::Error` is not implemented for `std::option::NoneError`
|
= note: required because of the requirements on the impl of `std::convert::From<std::option::NoneError>` for `SiteError`
= note: required by `std::convert::From::from`Do I have to write all the Maybe I should stick with |
This comment has been minimized.
This comment has been minimized.
I don't think the failure crate does that. But I could be wrong. |
This comment has been minimized.
This comment has been minimized.
cowang4
commented
Apr 4, 2018
|
Ok, so I re-examined the failure crate, and if I'm reading the documentation and different versions right, it's designed to always use the impl<E: StdError + Send + Sync + 'static> Fail for E {}https://github.com/rust-lang-nursery/failure/blob/master/failure-1.X/src/lib.rs#L218 And then a impl<F: Fail> From<F> for ErrorImplBut, it's just that since the error relevant to this rust issue, Thank you everyone for helping. I'm slowly learning Rust! |
This comment has been minimized.
This comment has been minimized.
|
@tmccombs I wasn't proposing an amendment to his proposal. |
This comment has been minimized.
This comment has been minimized.
|
The let IgnoreErrors = try {
error()?;
none()?;
};Proof-of-concept implementation using the same traits as before: https://play.rust-lang.org/?gist=e0f6677632e0a9941ed1a67ca9ae9c98&version=stable I think there's some interesting possibilities there, especially since a custom implementation could, say, only take results and bound |
This comment has been minimized.
This comment has been minimized.
|
I've had similar issues as those evidenced by @nikomatsakis using the existing version of the The trait definitions proposed by @scottmcm resolve these issues. They seem to be more complicated than necessary, however. I took a crack at reimplementing them and came up with the following: #[derive(Debug, Copy, Clone)]
enum ControlFlow<C, B> {
Continue(C),
Break(B),
}
// Used by `try { }` expansions.
trait FromTry: Try {
fn from_try(value: Self::Continue) -> Self;
}
// Used by `?`.
trait Try<T = Self>: Sized {
type Continue;
fn check(x: T) -> ControlFlow<Self::Continue, Self>;
}The main change is that the See the complete playground with implementations for |
This comment has been minimized.
This comment has been minimized.
|
Thanks for the report, @SergioBenitez! That implementation matches the "flip the type parameters" alternative version of the original trait proposal in RFC 1859: https://github.com/rust-lang/rfcs/blob/f89568b1fe5db4d01c4668e0d334d4a5abb023d8/text/0000-try-trait.md#unresolved-questions The biggest thing that loses is the property that
Certainly today From the perspective of the trait and its operations, however, I think "wrap a 'keep going' value in the carrier type" is an absolutely essential part of rust/src/libcore/iter/iterator.rs Lines 1478 to 1482 in 8728c7a If a function's going to return a carrier type, it's critical that there's a way to put the value of interest into that carrier type. And, importantly, I don't think providing this is a hardship. It has a very natural definition for Result, for Option, for Outcome, etc. @Centril has also pointed out before that "wrap a scalar in a carrier" is also a simpler construct theoretically. For examples, Haskell calls it the I'm also imagining a future where, like So all that is, unfortunately, more philosophical than concrete. Let me know if it made any sense, or if you think the opposite in any of the parts Edit: Apologies if you got an email with an early version of this; I hit "comment" too early... |
This comment has been minimized.
This comment has been minimized.
Ah yes, absolutely. Thanks for pointing that out.
Are there any specific examples of where it might be too restrictive?
I think that's a fair analysis. I agree. |
scottmcm
referenced this issue
Apr 30, 2018
Open
Make an "I just want to use `?`" type for use as a `main` return type #2367
This comment has been minimized.
This comment has been minimized.
|
@SergioBenitez From rust-lang/rfcs#1859 (comment)
So one could imagine a trait that moved both types to type parameters, like trait Try<T,E> {
fn question(self) -> Either<T, E>;
}And use that do enable all of let x: Result<_,_> = blah.poll()?; // early-return if NotReady
let x: Async<_> = blah.poll()?; // early-return if Error
let x: i32 = blah.poll()?; // early-return both NotReady and ErrorsBut I think that's definitely a bad idea, since it means that these don't work println!("{}", z?);
z?.method();Since there's no type context to say what to produce. The other version would be to enable things like this: fn foo() -> Result<(), Error> {
// `x` is an Async<i32> because NotReady doesn't fit in Result
let x = something_that_returns_poll()?;
}
fn bar() -> Poll<(), Error> {
// `x` is just i32 because we're in a Poll-returning method
let x = something_that_returns_poll()?;
}My instinct there is that the inference "flowing out of the ?" there is too surprising, and thus this is in the "too clever" bucket. Critically, I don't think that not having it is too restrictive. |
This comment has been minimized.
This comment has been minimized.
SoniEx2
commented
May 5, 2018
|
This is "interesting" https://play.rust-lang.org/?gist=d3f2cd403981a631f30eba2c3166c1f4&version=nightly&mode=debug I don't like these try (do catch) blocks, they don't seem very newcomer-friendly. |
illustrious-you
referenced this issue
May 24, 2018
Closed
RFC: Reserve `throw` and `fail` as keywords in edition 2018 #2441
This comment has been minimized.
This comment has been minimized.
|
I'm trying to assemble the current state of the proposal, which seems spread across multiple comments. Is there a single summary of the currently proposed set of traits (which drop the |
This comment has been minimized.
This comment has been minimized.
jmillikin
commented
Aug 10, 2018
|
Early on in this thread I see a comment about splitting It would be useful to have transparent conversion from
Semantically this feels like |
AGausmann
referenced this issue
Aug 18, 2018
Closed
Poll<T, E> = Result<Async<T>, E> vs Async<Result<T,E>> #141
Centril
added
T-lang
T-libs
labels
Sep 23, 2018
This comment has been minimized.
This comment has been minimized.
brunoczim
commented
Oct 20, 2018
|
I really think |
dwrensha
referenced this issue
Dec 2, 2018
Merged
Implemented support for Try trait to replace pry! for Promise #119
This comment has been minimized.
This comment has been minimized.
DJMcNab
commented
Dec 8, 2018
•
|
In working on https://github.com/rust-analyzer/rust-analyzer/, I have met a slightly different papercut from insufficiencies in the For this type, it it makes sense for match value? {
None => return Ok(None),
Some(it)=>it,
}where value?where enum NoneError<E> {
None,
Err(E),
}
impl From<T> for NoneError<T> {
fn from(v: T) -> NoneError<T> {
NoneError:Err(v)
}
}
impl<T, E> std::Ops::Try for Result<Option<T>, E> {
type OK = T;
type Error = NoneError<E>;
fn into_result(self) -> Result<Self::OK, Self::Error> {
match self {
Ok(option) => {
if let Some(inner) = option {
Ok(inner)
} else {
Err(NoneError::None)
}
}
Err(error) => {
Err(NoneError::Err(error));
}
}
}
fn from_error(v: Self::Error) -> Self {
match v {
NoneError::Err(error) => Err(error),
None => Some(None),
}
}
fn from_ok(v: Self::OK) -> Self {
Ok(Some(v))
}
}
impl<T> std::Ops::Try for Option<T> {
type OK = T;
type Error = NoneError<!>;
fn into_result(self) -> Result<Self::OK, Self::Error> {
match self {
None => Err(NoneError::None),
Some(v) => Ok(v),
}
}
fn from_error(v: Self::Error) -> Self {
match v {
NoneError::None => Some(None),
_ => unreachable!("Value of type ! obtained"),
}
}
fn from_ok(v: Self::OK) -> Self {
Ok(v)
}
}When asking @matklad why he couldn't create a custom enum implementing |
SergioBenitez
referenced this issue
Dec 11, 2018
Open
Uniform error handling through ? in handlers and FromDataSimple #857
This comment has been minimized.
This comment has been minimized.
|
Repost from #31436 (comment) because I wanted to comment on this, but I think that was the wrong issue: Essentially, a situation I have is callbacks in a GUI framework - instead of returning an let thing = match fs::read(path) {
Ok(o) => o,
Err(_) => return UpdateScreen::DontRedraw,
};Since I can't convert from a 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<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. |
This comment has been minimized.
This comment has been minimized.
|
@joshtriplett Sorry for taking a while to get back to this. I've put together a working prototype of the proposal at master...scottmcm:try-trait-v2 to be concrete. I hope to try out some more things with it. |
This comment has been minimized.
This comment has been minimized.
|
@scottmcm Do you have some higher-level explanation of the changes? |
This comment has been minimized.
This comment has been minimized.
|
@scottmcm FWIW I tried your changes in rayon too: |
This comment has been minimized.
This comment has been minimized.
omarabid
commented
Mar 4, 2019
|
So what is the solution to convert the Option NoneError? It seems that, because, it implements the Try Trait, it'll not compile unless you enable using that particular (unstable?) feature. So basically, you cannot use the ? operator with Option as far as I'm aware? |
This comment has been minimized.
This comment has been minimized.
|
@omarabid, the operator is stable for use with use std::fmt::Debug;
pub struct Error(Box<dyn Debug>);
impl<T: Debug + 'static> From<T> for Error {
fn from(error: T) -> Self {
Error(Box::new(error))
}
}
pub fn try_get<T>(slice: &[T], index: usize) -> Result<&T, Error> {
Ok(slice.get(index)?)
}@scottmcm, your prototype |
This comment has been minimized.
This comment has been minimized.
|
If we don't want my example to break, #[unstable(feature = "try_trait_v2", issue = "42327")]
impl<T, U, E: From<NoneError>> ops::Bubble<Result<U, E>> for Option<T> {
fn bubble(self) -> ops::ControlFlow<T, Result<U, E>> {
match self {
Some(x) => ops::ControlFlow::Continue(x),
None => ops::ControlFlow::Break(Err(E::from(NoneError))),
}
}
} |
scottmcm commentedMay 31, 2017
•
edited
The
Trytrait from rust-lang/rfcs#1859; implemented in PR #42275.Split off from #31436 for clarity (per #42275 (comment))