-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Allow ?-converting from Result<T, E> in functions returning Option<Result<T, E>> #99333
Allow ?-converting from Result<T, E> in functions returning Option<Result<T, E>> #99333
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @scottmcm (or someone else) soon. Please see the contribution instructions for more information. |
Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
I’m not sure if this needs an API change proposal, but I’m happy to create one if needed. |
@rustbot label +T-libs-api -T-libs |
This comment has been minimized.
This comment has been minimized.
The Nonetheless, this does seem potentially useful. The main downside I could imagine would be a context in which some errors need to get processed in a different way and then turned into |
I think that even if
I thought about it. I think this conversion as implemented follows the general pattern of “implicit conversions do not lose data”. It’s also very simple to go from match res {
Ok(val) => val,
Err(err) => return Some(Err(err)),
} and can’t be abstracted into a method. |
Convenient that I got randomly picked for this review 🙃 I was going to say the same thing that Josh did -- that I don't think experience has been kind to these complex implementations on I think the core problem is that the nestings don't permute well.
Hmm, interesting. (And coincidentally reminds me of https://swatinem.de/blog/fallible-iterators/ from this week's TWiR). Can you show some examples of the code you're thinking of using this? I'd like to experiment with other possibilities, like whether |
I don’t have public examples off the top of my head, but quick rg in GitHub search returns some more results:
I’d like to note that |
This comment was marked as resolved.
This comment was marked as resolved.
Such implementation is extremely useful when you are dealing with fallible iterators that return both use core::num::TryFromIntError;
let array: [Result<i16, TryFromIntError>; 4] = [Ok(1), Ok(-256), Ok(3), Ok(4)];
let mut iter = array.into_iter().filter_map(|rslt| {
let elem = match rslt {
Err(err) => return Some(Err(err)),
Ok(elem) => elem
};
let _u8 = match u8::try_from(elem) {
Err(err) => return Some(Err(err)),
Ok(elem) => elem
};
(_u8 % 2 == 0).then_some(Ok::<_, TryFromIntError>(_u8))
});
assert!(matches!(iter.next().unwrap(), Err(_)));
assert!(matches!(iter.next().unwrap(), Ok(4))); Just an example but there can be other combinators that involve use core::num::TryFromIntError;
let array: [Result<i16, TryFromIntError>; 4] = [Ok(1), Ok(-256), Ok(3), Ok(4)];
let mut iter = array.into_iter().filter_map(|rslt| {
let _u8 = u8::try_from(rslt?)?;
(_u8 % 2 == 0).then_some(Ok::<_, TryFromIntError>(_u8))
});
assert!(matches!(iter.next().unwrap(), Err(_)));
assert!(matches!(iter.next().unwrap(), Ok(4))); Taking aside macros, I am not aware of any other thing that improves the first snippet, therefore, this PR seems worthwhile. |
I have recenlty faced with the same issue while it's more convenient to use built-in implementation instead of writing the same code each time. Having such need as writing plenty of iterators (in my case it's scrapers) I obviously want to use fallible iterators because there is no other way to throw errors away. Sorry for not reproducible examples, it's just from the my current project let mut owners = asset.into_owners_crawler(self.graphql_client).into_iter();
let owner = match owners.next() {
Some(Ok(owner)) => match self.database.owner_exists_or_add(&owner, &asset_id) {
Ok(false) => owner,
Ok(true) => return Some(Err(ScrapeError::OwnerRepeat)),
Err(err) => return Some(Err(err.into())),
},
Some(Err(error)) => return Some(Err(error)),
None => unreachable!("owners seems to be empty, should not happened: {asset_id}"),
}; And after implementing let mut owners = asset.into_owners_crawler(self.graphql_client).into_iter();
let owner = match owners.next().transpose()? {
None => unreachable!("owners seems to be empty, should not happened: {asset_id}"),
Some(owner) => {
if self.database.owner_exists_or_add(&owner, &asset_id)? {
return Some(Err(ScrapeError::OwnerRepeat));
}
owner
}
}; Modified version seems to be more readable and convinient to work with. Moreover after modifing let mut owners = asset.into_owners_crawler(self.graphql_client).into_iter();
let owner = match owners.next().transpose()? {
None => unreachable!("owners seems to be empty, should not happened: {asset_id}"),
Some(owner) => {
self.database.add_owner(&owner, &asset_id)?;
owner
}
}; At this stage we have got rid of all unwanted pattern matching. If an our codebase built on top of the iterators, we write lots of unnecessary code, though, in a way, it could be solved with macros |
The implementation proposed in this PR is less problematic though. The issue with the controversial |
I am wondering if this implementation would be too specific, and if it'd make sense to try to find a way to be more generic. Probably not, because a more generic version would also apply to |
An unfortunate price to pay for stability. We already have inconsistencies like this such as how Replying to both @scottmcm and @c410-f3r, here's a version of the example @c410-f3r gave using try blocks like @scottmcm mentioned: use core::num::TryFromIntError;
let array: [Result<i16, TryFromIntError>; 4] = [Ok(1), Ok(-256), Ok(3), Ok(4)];
let mut iter = array
.into_iter()
.filter_map(|rslt| -> Option<Result<_, TryFromIntError>> {
try {
try {
let elem = rslt?;
let _u8 = u8::try_from(elem)?;
if _u8 % 2 != 0 {
return None;
}
_u8
}
}
});
assert!(matches!(iter.next().unwrap(), Err(_)));
assert!(matches!(iter.next().unwrap(), Ok(4))); vs use core::num::TryFromIntError;
let array: [Result<i16, TryFromIntError>; 4] = [Ok(1), Ok(-256), Ok(3), Ok(4)];
let mut iter = array.into_iter().filter_map(|rslt| {
let _u8 = u8::try_from(rslt?)?;
(_u8 % 2 == 0).then_some(Ok::<_, TryFromIntError>(_u8))
});
assert!(matches!(iter.next().unwrap(), Err(_)));
assert!(matches!(iter.next().unwrap(), Ok(4))); And to be clear, I'm not opposed, just hesitant. I think Mara's reasoning here is sound but I do want to be particularly cautious before introducing new |
We discussed this in today's @rust-lang/libs-api meeting. Based on @yaahc's suggested use of |
Hmm, the Another version of this would use use core::num::TryFromIntError;
let array: [Result<i16, TryFromIntError>; 4] = [Ok(1), Ok(-256), Ok(3), Ok(4)];
let mut iter = array
.into_iter()
.filter_map(|rslt| -> Option<Result<_, TryFromIntError>> {
let x: Result<_, _> = try {
let u8 = u8::try_from(rslt?)?;
(u8 % 2 == 0).then_some(u8)
};
x.transpose()
});
assert!(matches!(iter.next().unwrap(), Err(_)));
assert!(matches!(iter.next().unwrap(), Ok(4))); And, helpfully, if we get #98417 then I think that could simplify down to let mut iter = array
.into_iter()
.filter_map(|rslt| {
try {
let u8 = u8::try_from(rslt?)?;
(u8 % 2 == 0).then_some(u8)
}.transpose()
}); which might be sufficient. (The important thing there is that |
Independent of the
|
My problem is that I don't think this is what
I also don't agree with this point at all, and I feel that it's purely subjective, and likely based in a lack of familiarity because try blocks are still unstable. Footnotes
|
I don't want to reject this outright, but I do think we should hold off on possibly adding this until after try blocks have been stabilized for a while. Then we will be in a much better position to judge how necessary this impl is. |
Thanks for those concerns, if there is indeed a silent refactoring failure hidden inside this addition then I agree we shouldn’t do it. As far as I see it, and assuming no change to the function body:
Only the first case is a potential problem. I’d argue that due to
None of the examples above customarily fit on a single line — default and common formatting rules require three lines and added indentation for a block — which is an objective disadvantage compared to |
ping from triage: What's the status of this PR? It sounds like maybe it should be closed for now while try blocks are in the works? |
As far as I’m concerned no proposal has been put forward that offers to solve the issue using |
@m-ou-se I’m not sure what the status of it is. It feels rejected, but there was no formal rejection from libs team, so I’m kinda confused. What’s the verdict on this? |
not currently a libs team member and I'm not recently in the loop on this, but I can clarify the stance I was holding last time I commented on this PR. Given that this is an irreversible decision I think we need to maximize the amount of information we have before moving forward with this. I raised a blocking objection that is conditional upon |
I still don't see a good way to solve the issue with |
By way of one example, if we had generators and try, it'd be feasible to write |
We discussed this in today's @rust-lang/libs-api meeting. We were generally in agreement with @scottmcm's comment at #99333 (comment). We did look at the code samples for code that would be shortened by these We're hoping that some combination of @rfcbot close |
Team member @joshtriplett has proposed to close this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Thanks for clearing up the status on this PR. I hope generators will remove the |
+1 on close and to the concerns that @scottmcm raised here. In particular, we should add this impl only if we're also willing to support " Perhaps it goes without saying, but when this is needed, note that macros are always an option.1 E.g., we can of course write: macro_rules! otry {
($e:expr) => { match $e {
Err(e) => return Some(Err(e)),
Ok(x) => x,
}}
}
use core::num::TryFromIntError;
let array: [Result<i16, TryFromIntError>; 4] =
[Ok(1), Ok(-256), Ok(3), Ok(4)];
let mut iter = array.into_iter().filter_map(|rslt| {
let _u8 = otry!(u8::try_from(otry!(rslt)));
(_u8 % 2 == 0).then_some(Ok::<_, TryFromIntError>(_u8))
});
assert!(matches!(iter.next().unwrap(), Err(_)));
assert!(matches!(iter.next().unwrap(), Ok(4))); That has the same shape as the desired code in this comment. Footnotes |
And with postfix macros it could exist in the same position as |
Yeah, but macros kill autocompletion. I’m more hopeful about the use core::num::TryFromIntError;
let array: [Result<i16, TryFromIntError>; 4] =
[Ok(1), Ok(-256), Ok(3), Ok(4)];
let mut iter = gen {
for rslt in array {
let _u8 = try { u8::try_from(rslt?)? };
if _u8.is_ok_and(|x| x % 2 == 0) {
yield _u8;
}
}
});
assert!(matches!(iter.next().unwrap(), Err(_)));
assert!(matches!(iter.next().unwrap(), Ok(4))); In simple cases like this you could just replace Postfix macros would also solve this problem, because they should work with autocompletion, but I’m not sure that postfix macros will happen anytime soon, and there seems to be some progress on generators. |
The final comment period, with a disposition to close, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. |
This PR adds implementation
FromResidual<Result<Infallible, E>> for Option<Result<T, E>>
, allowing to use?
operator onResult<_, E>
in functions returningOption<Result<T, E>>
.Interestingly, this is already the case for
Poll<Option<Result<T, E>>>
, so this implementation seems to be accidentally missing and not deliberately left out.This is particularly useful for
Iterator
implementations whereItem = Result<T, E>
.This implementation is
const
when underlyingFrom<_>
impl isconst
. This is not the case forPoll<_>
implementations and I’m not sure why. Should I add missingconst
toPoll<_>
impls or there’s a reason for that?