Open ijackson opened 3 years ago
What is our general policy about ErrorKind variants ?
Just a bit of user feedback, this PR landing in beta broke our CI as we were asserting certain io Errors. I dunno if you can really call it a "language-breaking" change, but it did seem surprising that our CI would fail on beta due to rust lib changes.
https://doc.rust-lang.org/std/path/struct.Path.html#method.is_dir documents:
This is a convenience function that coerces errors to
false
. If you want to check errors, callfs::metadata
and handle itsResult
. Then callfs::Metadata::is_dir
if it wasOk
.
I’ve implemented this by coercing ErrorKind::NotFound
to false and propagating other errors. However some of my tests fail in cases where ErrorKind::NotADirectory
is (or would be) returned, showing that it should also be coerced to false.
This and the previous comment show that these variants are not like other unstable features that crates who don’t opt-in can pretend don’t exist, they can already be present in errors returned by the standard library. I’d like @rust-lang/libs to consider stabilizing them soon.
@TheBlueMatt, was your code that broke expecting ErrorKind::Other
or does this also affect values that previously were, well, other than Other
?
@TheBlueMatt, was your code that broke expecting ErrorKind::Other or does this also affect values that previously were, well, other than Other?
Yes, it was checking that an error was Other
. Still, that seems like a good enough reason that this can't be stabilized as-is? Its not unreasonably that some non-test code could be checking for Other
for one reason or another, no?
@TheBlueMatt Other
has been documented for some time as not something you should directly check for:
Errors that are Other now may move to a different or a new ErrorKind variant in the future. It is not recommended to match an error against Other and to expect any additional characteristics, e.g., a specific Error::raw_os_error return value.
https://github.com/rust-lang/rust/pull/85746/ is the change that made the standard library stop returning Other
, which was a prerequisite for adding new ErrorKind values.
We do want to stabilize these new variants soon. But in the meantime, if you have code that matches Other
and checks a specific underlying error code, you should be able to change that to match _
and check the specific error code.
@TheBlueMatt Other has been documented for some time as not something you should directly check for:
See-also discussion ending at https://github.com/rust-lang/rust/pull/79965#issuecomment-889382116, but the specific documentation that isn't only lightly implying this being an issue was added middle of last year (which is much more recent than our test code here). Indeed, it was implied earlier, but for those of us who weren't thinking really deeply about the implications of the struct-level docs it didn't come across entirely.
you should be able to change that to match _ and check the specific error code.
Right, we'd have to do even more platform-specific stuff, which I'm a bit hesitant to do, I guess we can just drop our tests, but I do worry about other code that may exist that isn't pure-tests from other projects.
I'm honestly somewhat surprised this is moving forward without some attempts at a rustc warning better communicating to users that the code they have written that compiles and runs is actually undefined behavior (not in the C sense of may-eat-your-cat, but in the may-change-in-the-future, behavior is not guaranteed sense). I understand that may not be practical to hit in all cases, but I imagine at least some direct match .. Other
or if == Other
things could likely be detected. In general, I am very surprised this doesn't fall under rustc's stability guarantees around running, operational, functional code in the wild not being broken by future versions of rustc.
Hi, @TheBlueMatt
As the author of one of the MRs which actually broke your test, I would like to say that I'm sorry about that. Unfortunately, risking breakage like yours seemed like the best option. I know it sucks when you're on the wrong side of a tradeoff like this - especially when it suddenly breaks your stuff.
You are right to say that it would have been better to have arranged to warn about this. But doing so in rustc is not currently straightforward. ErrorKind::Other
had to be a stable variant, because users of the stdlib need to be able to construct errors with that kind. But we do still need to be able to add variants, rather than having this enum frozen for all time.
Perhaps if we had thought of it sooner, a clippy lint would have made sense. But we didn't have one. (And maybe that wouldn't have helped you; not everyone runs clippy. I know I generally don't, despite feeling guilty about that...)
But given where we were, our sensible options were risking this kind of breakage, or postponing the addition of new ErrorKind
s (perhaps for some considerable time) while we did more preparation (eg trying to warn people).
My view is that the new ErrorKind
s are very sorely needed. For example, just this week I was writing a program that needs to read configuration from filesystem objects which might be files, or directories. The IsADirectory
error from File::open makes that more convenient to do portably and avoids having to ever discard an ErrorKind::Other
on the basis of complex reasoning about the filesystem state.
Whether avoiding a substantial delay to new ErrorKind
s was worth risking this kind of breakage seems like something reasonable people can disagree on. As others have said, one justification is that the documentation has always said that ErrorKind
might grow new variants, and to my mind the implication that those new variants would probably appear instead of Other
is obvious. I guess that is cold comfort to you.
Another mitigation is that even when this causes breakage, it is likely to be in tests (as in your case) - ie, the risk of bad code being deployed as a result is considerably lower than that of a test failure.
Having said all that: given this is only in beta, I think we do have the option to back this out ? If we did that, how long would we wait ? Who would write the missing clippy lint or whatever ?
Personally I'm torn. Rust's care for stability is one of its great attractions to me and I regret having undermined that for some of Rust's users. On the other had the previous ErrorKind
enum was IMO seriously deficient, and the change in my MR is precisely the kind of change which the docs have always explicitly said may happen (even if all the consequences have only been explicitly spelled out more recently).
I want to reply separately about what alternative idiom I think your tests should probably use.
On Thu, Jul 29, 2021 at 12:22:18PM -0700, Matt Corallo wrote:
you should be able to change that to match _ and check the specific error code.
Right, we'd have to do even more platform-specific stuff, which I'm a bit hesitant to do,
If you're not checking for any specific error code, then I don't think
it makes sense to check for Other
at all. That would inherently
break if a new ErrorKind variant were ever added. ErrorKind
is
#[non_exhaustive]
precisely to allow adding new error types; as a
result, asserting "this is an error type that doesn't have a more
specific enum variant" is inherently fragile and
non-forwards-compatible.
I guess we can just drop our tests, but I do worry about other code that may exist that isn't pure-tests from other projects.
It seems inherently unreliable to match Other
for anything other than
"some error I don't know about", given the above. Code doing that in a
match without a _
branch would already get a warning about
#[non_exhaustive]
. So the only way to have this happen would be an
==
or if let
or similar, or an assert.
We could add a clippy lint against asserts that match Other
without an
error code. That might have some false-positives, for code that actually
uses Other for something other than an unhandled errno value, but it
might still be viable as a clippy lint.
On Jul 29, 2021, at 17:01, Ian Jackson @.***> wrote: Hi, @TheBlueMatt
As the author of the MR which actually broke your test, I would like to say that I'm sorry about that. Unfortunately, risking breakage like yours seemed like the best option. I know it sucks when you're on the wrong side of a tradeoff like this - especially when it suddenly breaks your stuff.
First of all, it’s software, things happen, in our case it was just some minor check in a test, no sweat!
You are right to say that it would have been better to have arranged to warn about this. But doing so in rustc is not currently straightforward. ErrorKind::Other had to be a stable variant, because users of the stdlib need to be able to construct errors with that kind. But we do still need to be able to add variants, rather than having this enum frozen for all time.
Perhaps if we had thought of it sooner, a clippy lint would have made sense. But we didn't have one. (And maybe that wouldn't have helped you; not everyone runs clippy. I know I generally don't, despite feeling guilty about that...)
Indeed, sadly because clippy requires rustup I generally can’t use it anyway. :( But given where we were, our sensible options were risking this kind of breakage, or postponing the addition of new ErrorKinds (perhaps for some considerable time) while we did more preparation (eg trying to warn people).
I do wonder if it’s practical to do this in an edition? The next edition isn’t shaping up to be that far away, and adding this kind is breaking thing seems like a great candidate for such a thing! I’m not suggesting waiting for editions for every new variant, but doing the first new additions in an edition with more linting may avoid a lot of this. My view is that the new ErrorKinds are very sorely needed. For example, just this week I was writing a program that needs to read configuration from filesystem objects which might be files, or directories. The IsADirectory error from File::open makes that more convenient to do portably and avoids having to ever discard an ErrorKind::Other on the basis of complex reasoning about the filesystem state.
Makes sense, there’s quite a tradeoff here, but I do really worry about stability of rust here, and the guarantees around upstream not breaking existing code seemed stronger than they appear to be now. I suppose I just weigh stability differently. Whether avoiding a substantial delay to new ErrorKinds was worth risking this kind of breakage seems like something reasonable people can disagree on. As others have said, one justification is that the documentation has always said that ErrorKind might grow new variants, and to my mind the implication that those new variants would probably appear instead of Other is obvious. I guess that is cold comfort to you.
Partially. I agree that’s what it implies, but the explicit mention was only added very recently, and for those of us not super deep in the libstd world I guess it didn’t come across until now. Another mitigation is that even when this causes breakage, it is likely to be in tests (as in your case) - ie, the risk of bad code being deployed as a result is considerably lower than that of a test failure.
Having said all that: given this is only in beta, I think we do have the option to back this out ? If we did that, how long would we wait ? Who would write the missing clippy lint or whatever ?
I would think at least simple checks could happen in rustc and not clippy. Eg matching/comparing equality on a non-exhaustive enum with an “other” variant seems like something that rustc could warn on, though I admit I’m not on any way familiar with the rustc codebase, so feel free to tell me I’m wrong. Personally I'm torn. Rust's care for stability is one of its great attractions to me and I regret having undermined that for some of Rust's users. On the other had the previous ErrorKind enum was IMO seriously deficient, and the change in my MR is precisely the kind of change which the docs have always explicitly said may happen (even if all the consequences have only been explicitly spelled out more recently).
I want to reply separately about what alternative idiom I think your tests should probably use.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.
@TheBlueMatt Unfortunately, it isn't possible for us to make library changes like this in an edition. Editions can affect how code is compiled, but not the underlying behavior of the standard library, partly because you can have code from Rust 2015, 2018, and 2021 all compiled into one binary and sharing the same standard library.
For what it's worth, we absolutely value stability as critically important. In this case, we made a careful tradeoff, knowing that some code out there (especially test code) might be affected. We did consider making these alternatives insta-stable, but that still would have broken your test case because the enum variant would no longer match. We also considered "never extend ErrorKind again" as a potential alternative, but we felt that because ErrorKind
is #[non_exhaustive]
and because we don't expect non-test code to typically match on Other
in this fashion, we thought it would be reasonably safe to make this change.
One other note: https://github.com/rust-lang/rust/pull/85746 (which is the change that actually affected your test) is targeted at 1.55, and has a relnotes
tag; we're expecting to have a detailed release note explaining the situation for people. A release note does not ameliorate the breakage, but it should help communicate the nature and rationale and tradeoffs of the change.
In this case, we made a careful tradeoff, knowing that some code out there (especially test code) might be affected. We did consider making these alternatives insta-stable, but that still would have broken your test case because the enum variant would no longer match. We also considered "never extend ErrorKind again" as a potential alternative, but we felt that because ErrorKind is #[non_exhaustive] and because we don't expect non-test code to typically match on Other in this fashion, we thought it would be reasonably safe to make this change.
I'm curious if extending the #[non_exhaustive]
API in rustc to warn around matching Other
variants was considered? eg adding a tag to #[non_exhaustive]
to certain variants which indicates "generate a warning if this is matched on or compared against" at compile-time so as to make this exact language pattern better supported. I understand this is maybe part of the goal of #85746, but it does feel like an oversight in the expressiveness of the #[non_exhaustive]
, as mentioned in the description of #85746.
I'm curious if extending the
#[non_exhaustive]
API in rustc to warn around matchingOther
variants was considered? eg adding a tag to#[non_exhaustive]
to certain variants ...
No, we didn't think of that.
I understand this is maybe part of the goal of #85746, but it does feel like an oversight in the expressiveness of the
#[non_exhaustive]
, as mentioned in the description of #85746.
Now that #85746 has landed, I don't think this situation can arise again with ErrorKind
. In the future, new ErrorKind
s may indeed be added, for new kinds of underlying OS errors. But when that happens, the new kind will appear instead of Unknown
- not instead of Other
. Unknown
is permanently unstable so cannot be matched in tests (without turning on an unstable feature). So it would not be possible to write, on stable, a test which will break in the future.
I don't know if there are other enums in the stdlib with similar properties to ErrorKind
. I can't think of any offhand.
No, we didn't think of that.
I do wonder if it may make sense as a language feature in the future also for non-stdlib users. It doesn't feel strange to think that some crates may want a similar pattern. I admit the 85746 approach may make more sense to them, but there is no way to non-stabilize a particular enum variant outside of std, as far as I understand.
Unknown is permanently unstable so cannot be matched in tests (without turning on an unstable feature). So it would not be possible to write, on stable, a test which will break in the future.
Ah, maybe I partially misunderstood the way 85746 operates.
So in 1.55 this has broken our test suite for remove_dir_all by leaking into stable rather than being a nightly only feature.
It seems very poor form to return an enum variant to code which cannot match against it because the compiler considers it a nightly only feature. See https://github.com/XAMPPRocky/remove_dir_all/pull/33 : the failing jobs are failing in stable because the change isn't present there, but putting the change into stable code results in:
error[E0658]: use of unstable library feature 'io_error_more'
--> src/portable.rs:83:15
|
83 | &[io::ErrorKind::NotADirectory, io::ErrorKind::Other],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
To be really clear, my beef isn't with the breakage around Other, its that the new status quo means neither the old code nor the new code works. I'd like to see that remedied ASAP, since we can't run tests at all on stable today. Ironically, the tests are one @ijackson wrote ;)
@rbtcollins, to reiterate https://github.com/rust-lang/rust/issues/86442#issuecomment-889480955, nothing is supposed to be comparing equality to ErrorKind::Other
. Code that does this is wrong.
If the error kind is not one of the stable specific variants (non-Other
), the correct way for stable code to match it is _
and new unstable variants have no bearing on that as they would continue to match _
.
@dtolnay Ok, sure, we can fix this, probably by matching every other error type to exclude them but the definition has clearly shifted from 1.54 and before, and this leaves a pretty poor feeling here: https://blog.rust-lang.org/2014/10/30/Stability.html made a huge impression on me, and many others, and it seems like there was debate here, but the conclusion was to modify existing behaviour, rather than allowing edition based opt-in.
The 1.54 docs were:
Other
Any I/O error not part of this list.
Errors that are Other now may move to a different or a new ErrorKind variant in the future.
This is very different to "must use _ to match" which implies must exhaustively match every other Kind to be moderately sure the desired error has occurred - which I don't look forward to doing.
Whats the right way to have a discussion about the ergonomics here? 85746 seems like a really awkward interface : theres no way that I know of to ergnomically say 'match only unstable entries'. So when one is interested in a concrete error, that is currently behind an unstable feature flag, the choices are to either accept 'any random error', or list all-known-errors as failure cases.
If your code previously just checked that the error was ErrorKind::Other, without a more detailed assert (e.g., via the os error code), deleting that test seems like the right path forward, presuming you don't want to add platform-specific code to make the test actually confirm a specific error.
I think the test you cited is a little different -- it looks like it was intending to check that the error in question is not e.g. a permissions error or similar. There it seems like it makes more sense to either move the test toward using raw_os_error and checking a specific error code (of course, platform specific) or asserting that the test is not of some collection of particular variants (e.g., if the goal is to make sure the failure is not due to permissions errors...?).
The only real change after #85746 is that it's harder for someone to assert "this is an error std didn't know enough about to differentiate", but that doesn't seem like a particularly useful assertion for tests to make -- it doesn't test a specific error, only that it's not one of a very small limited subset.
Whats the right way to have a discussion about the ergonomics here?
The right way is to counterpropose some alternative way by which new error kinds can be accommodated as operating systems and standard library implementations evolve over time.
@Mark-Simulacrum Yes, we can either take on platform specific handling, or exclude all the known variants that this isn't; and previously we could do that in a pithy fashion by matching on Other exactly.
@dtolnay obviously ;) I meant - do I carry on here, or file a new bug, or create an RFC :- I'm not sure of the most effective way to engage.
Putting some principles forward, it should be possible to both do positive assertions - error is specifically X that we know, including some X that the stdlib hasn't yet categorised. And it should be possible for existing uncategorised things to be categorised better over time.
We can either consider better over time to be at most once per edition, or up to multiple times per edition.
At most once per edition is easy to reason about; there are no strong commitments that nothing will be incompatible between editions. It might be nice to do it more often though.
lets consider the dimensions we're dealing with.
The OS adds variants without coordinating with us, so any enumeration we have will be incomplete. We need to emit those when they happen though, which gives rise to Other and now Unknown.
Callers of APIs will rarely want 234 different code paths, so matching some set of cases and then a wildcard is common. non-exhaustive can be used to require this.
Some callers will be looking for those as-yet uncategorised cases, so they would like some way to match on those. If the uncategorised cases are in a hidden/unstable variant, thats impossible. If those cases are in an accessible variant, recategorising them will be an API break, requiring either callers to accept this is as API skew over time, or for us to only do this at most once per edition. Or recategorising has to be done by some specialisation within the uncategorised bucket.
So one possibility occurs to me:
Have an IoErrorKind::Uncategorised(UnstableCategory)
UnstableCategory is an enum which grows over an edition, and the contents moved to IoErrorKind each edition.
#[non_exhaustive]
enum UnstableCategory {
Uncategorised,
...
}
Then consuming code that wants to match a currently uncategorised error:
match kind {
IoErrorKind::Uncategorised(_) => { println!("expected case"); }
_ => { panic!("unexpected error occured"); }
}
If a point release of Rust categorises that error - lets say NotADirectory, then the following happens:
#[non_exhaustive]
enum UnstableCategory {
Uncategorised,
NotADirectory,
...
}
And the user side code doesn't break. The user can opt in to tighter matching if they wish, either with some build.rs that sets a feature based on the compiler version, or a MSRV.
match kind {
IoErrorKind::Uncategorised(UnstableCategory::NotADirectory) => { println!("expected case"); }
_ => { panic!("unexpected error occured"); }
}
How is this different to the status quo? All stable Kinds are outside the space matched on, so trying to do this at the IoErrorKind layer would fail: it would bring in all known kinds, and matching just the placeholder for uncategorised kinds fails if we try to categorise directly by moving things outside of that cat4egory.
error is specifically X that we know, including some X that the stdlib hasn't yet categorised
Allowing code to match "something the standard library hasn't yet categorized" would set that code up to be broken when the standard library does start categorizing it in the future.
And it should be possible for existing uncategorised things to be categorised better over time.
That's exactly the problem: we need to make sure that existing code doesn't break when we add new categorization.
We did that by ensuring the only way to match "unknown error" is with a wildcard (_
), so that when the standard library adds new categorization your code doesn't break.
We were faced with a three-way choice: never categorize any additional errors (not an option), or require people to change their code every single time we added a new categorization, or require people to change their code once if they're currently using ErrorKind::Other
. And the documentation already warned against matching ErrorKind::Other
in that way.
At most once per edition is easy to reason about; there are no strong commitments that nothing will be incompatible between editions. It might be nice to do it more often though.
In addition to the issue of doing it more often, we also can't tie this to an edition. io::ErrorKind
can't be a different type in different editions, for a variety of reasons but in particular because one Rust program can include code from multiple editions.
Also, the change you're proposing of adding something like ErrorKind::Uncategorized
is effectively what we did, and it would cause exactly the same issue with any code currently matching ErrorKind::Other
.
Here's another way to look at the change:
_
to match uncategorized errors. When a new ErrorKind is added, your code will continue to work because it matches _
. When that new ErrorKind is stabilized, your code will still continue to work, because it matches _
._
, but you have the option of starting to match new ErrorKind variants as soon as we add them, without waiting for them to be stabilized.@joshtriplett
Allowing code to match "something the standard library hasn't yet categorized" would set that code up to be broken when the standard library does start categorizing it in the future.
I specifically proposed a mechanism that wouldn't break the code (within the same edition) if the stdlib started categorising it - but only sustainable for a relatively short period, because eventually the number of newly categorised errors will be large enough that people will want to match on the specific sub-variant.
re: needing the same errorkind across editions - ah, thats a constraint I hadn't internalised. If I can think of a way forward I'll suggest it, but thats a pretty hard obstacle.
Yes, I understand what has been done so far. It has removed a useful capability that existed before. I find it frustrating that most people seem to blithely assert that everything is Just Fine and that the new situation is better - only @Mark-Simulacrum has actually picked up on the impact so far.
@rbtcollins I appreciate that the mechanism you're suggesting wouldn't necessarily break code when categorization was added (though people would have to change code to match if the categorization was moved to the top level alongside other variants). But the mechanism you're suggesting would break code when the mechanism itself was introduced, in exactly the same way that was already done. With the approach that was implemented, code that matched ErrorKind::Other
has to match _
instead; with the approach you're suggesting, code that matched ErrorKind::Other
would have to match ErrorKind::Uncategorised(_)
instead. Either way, existing code that uses ErrorKind::Other
would break. The approach we used was to allow the introduction of new variants in the future without any further breakage.
And to be clear, I'm not suggesting this is an ideal solution or that it has zero impact, just that it seemed like the best of the solutions we had available.
std::io::ErrorKind::Foo
for manyFoo
. I will paste a list here when the MR is merged :-).
PR has been merged for a while ;)
~Hey, I was wondering if these extra error kinds are added in std lib for stable or not?~
~I am using rustc 1.56.1 and trying to create a file in a directory (using fs::OpenOptions) gives me ReadOnlyFilesystem
error,~ ~(which is expected as the fs is supposed to be readonly,) but if I try error.kind() == std::io::ErrorKind::ReadOnlyFilesystem
~
~I get compiler error use of unstable library feature
.~
~Is there a way I can check if the error I get is of this specific kind without using nightly?~
~The way I understood rust release process I thought unstable features were nightly -> beta -> stabilized , but in this case it~ ~seems half of this is in stable and half is still unstable? Apologies if I misunderstood anything.~
~To elaborate, my current setup is~
if kind == ro_fs{
// do something
}else{
// do something else
}
~I am compiling with rustc v 1.56.1, for target x86_64-unknown-linux-gnu, with rustflags -C target-feature=+crt-static
on Linux OS.~
Thanks :)
EDIT/UPDATE : on reading the discussion above once again, I figured out I can use the same way that TheBlueMatt has used and use raw_os_error, so that might work in my case. Thank you.
@joshtriplett well, I'm finally getting time to do any rust, and the first thing is fixing up the still broken tests in remove_dir_all, and that means figuring this out. 6 months later and its still not usable on stable, so I'll need to use raw os errors indefinitely.
I still maintain that this is a stability break, no matter how desirable the feature. https://blog.rust-lang.org/2014/10/30/Stability.html
Code that worked, that was written with the API of the time, now doesn't work. We've pushed work onto our users.
We should update the stability guarantee I think - it was inspirational, but we're not living up to it.
/end whinge
@rbtcollins I think you should use raw OS errors, yes.
I think this is an opportunity to plug my blog post about this issue where I go into some detail about what the problems are, why there aren't easy answers, and what to do if you're trying to fix things.
In #90706 I proposed to add a link to my blog post. That was felt to be inappropriate. Instead in #94273 a very abbreviated version of my recommendation was added. I still think this is a complicated and difficult issue and it is a shame that the official documentation still doesn't really reference a thorough explanation for people in @rbtcollins's situation, who are quite understandably frustrated.
I wonder if adding to that prose a comment about raw errors would be useful?
I think there are three cases: One knows the error, and the version of rust at the time the code is written knows it: match on exactly that error.
One knows the error, and the version of rust at the time the code is written does not know it: use raw os errors and platform specific code to match on exactly that error.
One doesn't know the exact error: match on the inverted set of all the errors you know it cannot be (and if some of those are not known to rust, perhaps do so with raw os errors).
Personally, I want this feature to get stabilized.
There is no way for a lib to construct an error like IsADirectory
with error context in stable rust as described in https://github.com/datafuselabs/opendal/pull/221.
I am running into some similar problems. It seems that std::fs::read_to_string
can potentially return an Err<std::io::Error>
with ErrorKind::IsADirectory
as its .kind()
.
I can use a _
wildcard match to handle it and display a generic error (eg. eprintln!("{:?} IO Error", kind)
), but even though doing that will print "IsADirectory IO Error" to stderr on stable, I can't actually match against it on stable.
That this feature exists is becoming annoying in my everyday work, because my editor will suggest that I use (for instance) InvalidFilename
and then turn around and tell me I'm not allowed to use InvalidFilename
because the feature isn't stabilized. I rarely express the opinion that an unstable feature is somehow harmful but, in this case, I'm starting to feel it's time to "act" or get off the pot, so to speak.
@archer884 considering there are other unstable APIs in std, that seems more like an issue for editor plugins rather than motivation to stabilize or remove this nightly feature. Ideally completions would hint if something is unstable or hide them entirely (either on stable toolchain or configured to hide). There are open issues to improve this situation in rust-analyzer
and intellij-rust
, such as https://github.com/rust-lang/rust-analyzer/issues/3020 and https://github.com/intellij-rust/intellij-rust/issues/4439.
@archer884 considering there are other unstable APIs in std, that seems more like an issue for editor plugins rather than motivation to stabilize or remove this nightly feature. Ideally completions would hint if something is unstable or hide them entirely (either on stable toolchain or configured to hide). There are open issues to improve this situation in
rust-analyzer
andintellij-rust
, such as rust-lang/rust-analyzer#3020 and intellij-rust/intellij-rust#4439.
As I mentioned here, this affects my Rust code itself, independent of my IDE. Functions such as std::fs::read_to_string
are able to return errors (on stable) that are a part of the io_error_more
unstable API, but they can't be directly matched against (except with a wildcard match) because they are unstable. But I can call methods like .to_string()
, which work on the unstable variants. (eg. err_object.kind().to_string()
can output "is a directory"
, even though ErrorKind::IsADirectory
is unstable)
Proof of concept using evcxr_repl
:
>> // The root directory `/` is always not a file. This example may not work on Windows.
.. if let Err(e) = std::fs::read_to_string("/") {
.. println!("{}", e.kind().to_string()); // `e.kind()` will be a `std::io::ErrorKind::IsADirectory`
.. }
is a directory
()
>> std::io::ErrorKind::IsADirectory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
use of unstable library feature 'io_error_more'
I'm sure that you know way more about Rust then I do, but to me it seems weird that even if #![feature(io_error_more)]
is not enabled it can still exist in a limbo state where it can't be referenced directly but can be used.
I'm sure that you know way more about Rust then I do, but to me it seems weird that even if
#![feature(io_error_more)]
is not enabled it can still exist in a limbo state where it can't be referenced directly but can be used.
@superatomic apologies if I wasn't clear. I was addressing a general (not unique to this feature) issue about editor suggestions of unstable parts in std, but I wasn't implying what should be done with #![feature(io_error_more)]
. As you and others note, there are issues specific to this feature that motivate actions such as stabilization.
I also want to vouch for stabilization as soon as possible. The existence of these unstable enum variants would be fine, if the creation of them was also "unstable only". Right now the unstable variants can be created in the internals of std
in stable, without being able to create these same variants externally in stable. It makes mocking APIs, testing error handling and various other things very challenging.
We are going to need to implement some rather elaborate io::Error
wrapping systems in the Deno project now to be able to manually create io::Error
-likes for the unstable ErrorKind
variants.
Ooh, this is a fun workaround for creating unstable ErrorKind
s, in stable: std::io::Error::from_raw_os_error(libc::EISDIR)
. Very hacky :D
Considering the ErrorKind
can already be created (and matched via the stringified form) in stable, this may aswell be stable already. Updating the behavior here would likely break a considerable amount of existing applications which may unintentionally rely on these unstable error kinds already.
It seems like most of the recent comments are only about IsADirectory
. Since that makes clear it's a useful and wanted error kind, it might make sense to send a PR for stabilizing that one specifically. Then that doesn't have to be blocked on the other variants being stabilized.
The existence of these unstable enum variants would be fine, if the creation of them was also "unstable only".
Any uncategorized error is unstable in the same way, regardless of whether its ErrorKind is ErrorKind::Uncategorized or a specific unstable one like ErrorKind::IsADirectory. That we internally already (experimentally) split off IsADirectory from Uncategorized shouldn't make any difference for Rust programs, as the result is the same: An io::Error with an unmatchable ErrorKind. (Which is exactly the intention, since we haven't categorized them yet, no one should match on them, as that would break once we do put them in a stable category/kind.)
It seems like most of the recent comments are only about IsADirectory. Since that makes clear it's a useful and wanted error kind, it might make sense to send a PR for stabilizing that one specifically. Then that doesn't have to be blocked on the other variants being stabilized.
That'd be great. We care mostly about IsADirectory
, NotADirectory
, and FilesystemLoop
for our use cases right now.
It seems like most of the recent comments are only about
IsADirectory
. Since that makes clear it's a useful and wanted error kind, it might make sense to send a PR for stabilizing that one specifically. Then that doesn't have to be blocked on the other variants being stabilized.
I support stabilizing IsADirectory
, but I highly suggest that we stabilize NotADirectory
at the same time as they are opposites of each other. (like @lucacasonato said)
It might also be worth stabilizing DirectoryNotEmpty
as well, but it doesn't appear as important and is only partially related (it's the only other directory-related error, but it doesn't deal with the existence of directories).
The existence of these unstable enum variants would be fine, if the creation of them was also "unstable only".
Any uncategorized error is unstable in the same way, regardless of whether its ErrorKind is ErrorKind::Uncategorized or a specific unstable one like ErrorKind::IsADirectory. That we internally already (experimentally) split off IsADirectory from Uncategorized shouldn't make any difference for Rust programs, as the result is the same: An io::Error with an unmatchable ErrorKind. (Which is exactly the intention, since we haven't categorized them yet, no one should match on them, as that would break once we do put them in a stable category/kind.)
Is there some way to prevent or warn about the use of ErrorKind::Other
and ErrorKind::Uncategorized
? Perhaps a Clippy lint like @ijackson was talking about might make sense.
Returning unstable variants is still not ideal even if we should treat them like ErrorKind::Other
in stable code and just use wildcards. I think the biggest potential problem is that returning unstable varients implies that they are stable (i.e. matching ErrorKind::Other
is bad form, but still possible). It might not be ideal to change which variant is returned depending on whether #![feature(io_error_more)]
is used, but it might be worth only splitting errors off from ErrorKind::Uncategorized
when io_error_more
is enabled, instead of always.
Is there some way to prevent or warn about the use of ErrorKind::Other and ErrorKind::Uncategorized?
Using Uncategorized
already gives an error:
error[E0658]: use of unstable library feature 'io_error_uncategorized'
--> src/main.rs:2:13
|
2 | std::io::ErrorKind::Uncategorized;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Or do you mean something else?
I think the biggest potential problem is that returning unstable varients implies that they are stable
I'm not sure I understand. ErrorKind
is #[non_exhaustive]
, so you'll always have to take into account that it might have more variants than you can't match on. Whether some of those have an unstable/experimental name or not shouldn't make any difference.
change which variant is returned depending on whether
#![feature(io_error_more)]
is used
What would happen if an (indirect) dependency enables that feature, but your own crate does not? (Or the other way around?) Especially if that dependency ends up creating an Error or ErrorKind to your crate. An entire Rust program (which can be a combination of many crates) uses a single copy of std
, so we can't make std
behave differently per crate.
I prefer to stabilize the following error kind:
NotADirectory
IsADirectory
DirectoryNotEmpty
The reasons are listed as follows:
They are normal
Any call to remove_dir
or create_dir
could trigger them unless we check metadata
every time we try to call std::fs
functions.
They are actionable
Developers have a strong need to check and match them.
Think about we are developing command-line tools. We may want to output friendly messages if users' input path is a directory.
References:
The only thing I want to know before stabilizing any of them, is if they make sense on all/most platforms. E.g. ideally, trying to remove a non-empty directory would result in the same DirectoryNotEmpty error kind on all platforms.
This doesn't necessarily have to be a strict requirement. There might be cases where one OS simply does not have errors that are specific enough. But in those cases we should be extra careful, and wonder if we're making our error kinds too specific.
The only thing I want to know before stabilizing any of them, is if they make sense on all/most platforms. E.g. ideally, trying to remove a non-empty directory would result in the same DirectoryNotEmpty error kind on all platforms.
Nice catch. The three errors are obvious on posix platforms like linux, darwin, and bsd.
Windows also have some compatibility with the UNIX family of operating systems, which is the subset of errno.h
.
References: https://docs.microsoft.com/en-us/cpp/c-runtime-library/errno-constants?view=msvc-170
Is there some way to prevent or warn about the use of ErrorKind::Other and ErrorKind::Uncategorized?
Using
Uncategorized
already gives an error:error[E0658]: use of unstable library feature 'io_error_uncategorized' --> src/main.rs:2:13 | 2 | std::io::ErrorKind::Uncategorized; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Or do you mean something else?
Oops, I didn't check to see if it already issued a warning. ErrorKind::Uncategorized
is not a problem.
Should ErrorKind::Other
behave in the same way, though?
Currently, Rust doesn't warn about using it, even when it used in a pattern match.
I think the biggest potential problem is that returning unstable variants implies that they are stable
I'm not sure I understand.
ErrorKind
is#[non_exhaustive]
, so you'll always have to take into account that it might have more variants than you can't match on. Whether some of those have an unstable/experimental name or not shouldn't make any difference.
Sorry, I should have been clearer about what I was talking about.
If a programmer understands that the unstable ErrorKind
enum variants should be treated as ErrorKind::Other
and only matched via wildcards, there isn't a problem.
However, because these variants are given specific names and descriptions, and return based on certain contexts, it gives off the appearance of being usable.
Basically, it's not immediately obvious that the unstable variants should be ignored and simply wildcard matched.
change which variant is returned depending on whether
#![feature(io_error_more)]
is usedWhat would happen if an (indirect) dependency enables that feature, but your own crate does not? (Or the other way around?) Especially if that dependency ends up creating an Error or ErrorKind to your crate. An entire Rust program (which can be a combination of many crates) uses a single copy of
std
, so we can't makestd
behave differently per crate.
I didn't know that std
worked like that. I agree, it shouldn't return different variants. Sorry!
This issue is really starting to bug me as it breaks some existing code and, as others have said, prevents from fixing it.
I have a specific scenario where I need to move a file to a different filesystem, the only reliable cross-platform way I've found to do it is to fs::rename
and check the resulting error.
Now this doesn't work because the ErrorKind:CrossesDevice
variant has been introduced. I don't like silent breakages like this one but I understand how they are necessary.
The problem I have is that the code can't be fixed, I now need to check for this specific error variant but I can't because the feature is unstable and won't compile by definition on stable. So my program doesn't work anymore and I don't have any reliable way to make it work without using some huge hacks that may completely break in the future.
I don't know what solution could be brought - I thought about adding a function that would convert the new variants to the old one, but it would of course be unstable for a bit of time so that wouldn't solve the problem at all.
Do you have any idea of improvements to solve this problem?
First of all, as advised by @m-ou-se, we are on the way to stabilizing part of io_error_more
instead, like NotADirectory
and DirectoryNotEmpty
. This can prevent us kept blocking without any progress. And we will not stabilize ErrorKind:CrossesDevice
for now. (Please correct me if I understand wrongly).
Then, for this problem:
I need to move a file to a different filesystem, the only reliable cross-platform way I've found to do it is to fs::rename and check the resulting error.
Which variant do you check before?
As described in docs,
Errors from the standard library that do not fall under any of the I/O error kinds cannot be matched on, and will only match a wildcard (_) pattern. New ErrorKinds might be added in the future for some of those.
To me, the clearest way is falling back to copy&delete
(just like mv
does) while meeting errors except NotFound
, PermissionDenied
, and others already known (and stable) error variants.
Currently, Rust doesn't warn about using it, even when it used in a pattern match.
Maybe it's worth adding a Clippy rule for matching ErrorKind::Other
.
Proposed new lint at https://github.com/rust-lang/rust-clippy/issues/9004
Nice answer, thank you.
May I ask why ErrorKind
isn't marked as #[non_exhaustive]
given new variants can be added anytime and ErrorKind::Other
shouldn't be matched against?
May I ask why
ErrorKind
isn't marked as#[non_exhaustive]
given new variants can be added anytime andErrorKind::Other
shouldn't be matched against?
ErrorKind
does marked as #[non_exhaustive]
:
Reference: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
Oh yes sorry, I confused #[non_exhaustive]
and having a non-matchable variant.
Nevermind.
This doesn't necessarily have to be a strict requirement. There might be cases where one OS simply does not have errors that are specific enough. But in those cases we should be extra careful, and wonder if we're making our error kinds too specific.
Highly relevant to this concern is the discussion starting here: https://github.com/rust-lang/rust/pull/79965#issuecomment-832771472
In particular, I would like to (re)propose the principles I set out here https://github.com/rust-lang/rust/pull/79965#issuecomment-833422225
The only thing I want to know before stabilizing any of them, is if they make sense on all/most platforms. E.g. ideally, trying to remove a non-empty directory would result in the same DirectoryNotEmpty error kind on all platforms.
I think what you mean is whether there are platforms where you can try to remove a non-empty directory, and get an error because it was non-empty, and not be able to tell that that was the reason ?
I think if there are any such operating systems then they ought to either not implement these parts of std
, or ought to be at a low support tier where it is expected that Rust programmers might have to do platform-specific work.
The alternative is to say that all Rust programmers even on popular and sensible operating systems must apply platform-specific workarounds (involving raw OS error numbers), or write unnatural code, or be exposed to toctou races.
Feature gate:
#![feature(io_error_more)]
This is a tracking issue for many
io::ErrorKind
variants being added in #79965.The additions were motivated by a perusal of Unix standards, but corresponding mappings for Windows were included in that PR.
Public API
std::io::ErrorKind::Foo
for manyFoo
. I will paste a list here when the MR is merged :-).Steps / History
Unresolved Questions
ErrorKind
variants ? It may not be necessary to resolve this wider question in order to stabilise this particular set ofErrorKind
s.