-
Notifications
You must be signed in to change notification settings - Fork 125
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
Add backtrace support to derive-Error macro #110
Add backtrace support to derive-Error macro #110
Conversation
@@ -133,7 +133,7 @@ required-features = ["display"] | |||
|
|||
[[test]] | |||
name = "error" | |||
path = "tests/error.rs" | |||
path = "tests/error_tests.rs" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to change file name, as I've moved tests to separate module and Rust won't allow me to have mod error
in error.rs
.
For the last two questions, you can revert part of this commit: For your first issue I think there's two issues:
FYI I won't have time to review this until mid december. |
I thought the same at first, but that's not entirely true. Consider this situation: #[derive(Error)]
struct MyError {
source: SomeOtherError,
} Now, consider, that It's not the biggest problem, but if there is a way to properly feature-gate generated |
Seems reasonable. I'll improve this part. But the issue still stands even for two fields case. Consider: #[derive(Error)]
struct MyError(#[error(not(backtrace))] SomeOtherError, Backtrace); Specifying However, in this case, specifying valid attribute on a source-field will mark all other fields as "disabled" and we'll get nothing. |
It's absolutely hacky. I just put it there as a temporary solution, as reworking |
Fixed. For tuple-structs/variants with 1 fieldOnly infer field as a source-field if it's not "inferrable" as backtrace-field. #[derive(Error)]
// This field WON'T be inferred as source.
struct MyError(#[error(not(backtrace))] Backtrace);
#[derive(Error)]
// This field WILL be inferred as source.
struct MyError(#[error(not(backtrace))] AnyOtherType);
#[derive(Error)]
// You can always mark any field as source explicitly.
struct MyError(#[error(source)] Backtrace); For tuple-structs/variants with 2 fieldsIf there is a backtrace-field found (either inferred, or explicitly specified via attribute), infer other field as a source-field, unless it's explicitly marked with #[derive(Error)]
// Infers AnyOtherType as source-field
struct MyError(AnyOtherType, Backtrace);
#[derive(Error)]
// Also infers AnyOtherType as source-field
struct MyError(AnyOtherType, #[error(backtrace)] MyBacktrace);
#[derive(Error)]
// Infers Backtrace as source-field
// Kinda unexpected, but the whole case is strange itself
// As far as I see, it's the only such case for 2-fields tuple-struct/variant
struct MyError(Backtrace, #[error(backtrace)] MyBacktrace); |
I've experimented a bit with generating feature-gated code and come to conclusion that it's impossible to do properly in our case. It's possible to generate feature-gated code. And using I suppose we should add separate And tests should stay as is (i.e., feature-gated by And then we can also optionally revert |
Hmm, this indeed sounds like a shitty situation. One thing that would solve
it though afaict, is if the default implementation of backtrace would
forward to self.source() instead of always returning None. I think that
would be a very reasonable thing to do. Could you create a PR for this on
rust itself? This should still be possible to change there, since it is
still an unstable API.
…On Fri, 22 Nov 2019, 19:52 Roman Titov, ***@***.***> wrote:
I've experimented a bit with generating feature-gated code and come to
conclusion that it's impossible to do properly in our case.
It's possible to generate feature-gated code. And using rustversion crate
it's even possible (not at all trivial, though) to feature-gate by compiler
channel. But in the end it's impossible to feature-gate by *enabled
unstable feature*. So, we can't generate implementation that would
automatically become available when #![feature(backtrace)] is enabled.
I suppose we should add separate error-backtrace (or just backtrace)
feature and apply it in implementation instead of nightly
<https://github.com/JelteF/derive_more/pull/110/files#diff-9a269209bed069ffb36d5565a91c9840R241>
.
And tests should stay as is (i.e., feature-gated by nightly and #![cfg_attr(feature
= "nightly", feature(backtrace)]).
And then we can also optionally revert rustc_version check in build.rs
and force-enable nightly feature from there so that full test-suite is
*guaranteed* to run on nightly.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#110?email_source=notifications&email_token=AAI3YJTH6OPLIUTTODFK5NTQVAS65A5CNFSM4JQF4UR2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEE6Q6WY#issuecomment-557649755>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAI3YJTI3IJ4J34XKURPTDLQVAS65ANCNFSM4JQF4URQ>
.
|
I've seen your questions on RFC 2504. What's your current opinion on this PR? I've had a discussion with @tyranron and we think, as I'll re-review the PR and probably try to chart all implicit/explicit source/backtrace permutations, to ensure we know what expected behaviour is in each case. Considering the issue I've explained in this comment, I think we should just accept it and go with "no backtrace by default, enabled with explicit |
I've refreshed my context. Seems like tests I've made are good enough specification of expected behaviour. Considering my proposal to "not generate backtrace method implementation implicitly when there is only source-field, but no backtrace-field": We have to change implementation (and tests) in such way, so:
We also have to make sure that we do actually chain backtrace calls in cases when:
But current test suite should catch such errors. As far as I can see, this should not be too difficult to implement and it also won't introduce any "impossible-to-specify" or "ambiguous" attribute combinations. |
Thanks for picking this up again. Based on the discussion I've had on RFC 2504 I actually think that it would be better if we don't implement any backtrace forwarding. This is an antipattern that I don't think this library should try to make easy. So i think we should only support a backtrace field, inferred or explicitely specified in cases where it cannot be inferred. |
I have mixed ideas in this regard. It may be an antipattern, but:
@tyranron may also have some valid arguments. |
I think the responses of @mitsuhiko explain why it's an anti pattern pretty
clearly here: rust-lang/rust#53487
But for completeness I'll reword it here too. The main reason why it's an
anti pattern, is that it causes problems if you want to show the whole
error chain (which is usually what you want). In that case you suddenly
cannot show all the stacktraces in the chain, because there will be
duplicate ones in the chain.
Users don't really care about easy backtrace forwarding. They care about
easy printing of an error chain with backtraces. As long as you don't
implement backtrace forwarding, this function is very easy to implement. If
you don't it becomes much harder/impossible. I'd like this library to help
bring the ecosystem in a good state, where we have error chains that are
easy to display nicely. Not the other way around.
So especially with regards to backwards compatibility: I think we should
remove backtrace forwarding. Removing it later would be a breaking change.
Adding it later should be no problem, if it turns out that we do need
forwarding.
…On Tue, 14 Jan 2020, 20:54 Roman Titov, ***@***.***> wrote:
This is an antipattern that I don't think this library should try to make
easy.
I have mixed ideas in this regard.
It *may* be an antipattern, but:
-
so far the only compelling case I've seen when such behaviour may be
harmful is when we have an *'error with backtrace'* have an *'error
with backtrace from another thread'* as its source
-
though I'd assume it's less common case
-
backtrace forwarding can be disabled with attributes, so
derive-Error still can be used for such errors with very moderate discomfort
-
backtrace-chaining is, probably, the most common case of all
-
std::backtrace::Backtrace is still unstable and it's unclear for me if
community would settle for *'backtrace chaining as usual default'* or *'std::error::Error::backtrace
never chains, chaining implemented with extension-traits'*
- and, if backtrace-chaining happens to be widespread, being a purist
against a pattern used by most of community seems impractical
-
dtolnay decided to implement backtrace-chaining in thiserror, so, at
least one respected Rust developer thinks it's not that bad
- 'argument from authority' is a logical fallacy, but still...
-
we can always change it later, if it would prove to be a faulty
decision
- though, I recognize, that breaking changes for a crate with 1.5
millions downloads may be a potentially questionable decision
@tyranron <https://github.com/tyranron> may also have some valid
arguments.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#110?email_source=notifications&email_token=AAI3YJQQAMYT7VKUKCMF3U3Q5YJ6BA5CNFSM4JQF4UR2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEI542II#issuecomment-574344481>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAI3YJS2TI7OFWNMVO7WNMLQ5YJ6BANCNFSM4JQF4URQ>
.
|
@JelteF I understand why backtrace forwarding is an anti-pattern when is applied blindly and by default. I agree. Let's just not apply any backtrace forwarding by default. But I cannot understand why we should remove the ability when library user really requires backtrace forwarding in his case? Let's make it just opt-in: apply the forward behavior only if |
Do you want to use the forwarding feature yourself? If so in what case? Because if not, I'd much rather have only features that are recommended in |
I think it's good to restrict people in their capabilities of shooting themself and the rust ecosystem in the foot. |
@JelteF OK then... I'll try to explain in details our position and errors usage in our codebase.
In our codebase (~30K CLOC) we have zero cases of showing the whole error chain separately error-by-error, which is quite illustrative that we do not need it at all. What we do instead, is:
I understand that this way of "doing errors" may be somewhat different from what @mitsuhiko explained, because the problems he has described are non-problems for us. But I cannot call our way of "doing errors" non-idiomatic or bad, just because it plays too well for us. And vice versa: I don't see any reason to propose our way of "doing errors" as the most idiomatic and best one, because it plays quite well only for our backend app case, and may be quite inconvenient for library cases or apps of different kinds.
So, yes. We definitely wanna use forwarding for our codebase. We will use it anyway, but we want And that's why I'm talking about giving that ability for |
Thanks a lot for the clear explanation. One problem I see with your current approach as is that if you will get a problem if a an Error type from one of your dependencies implements If you would replace your fn first_backtrace(err: Error) -> Option<Backtrace> {
while err.backtrace() != None {
if err.source() == None {
return None
}
err = err.source()
}
return err.backtrace()
} If you use that you also don't need backtrace forwarding on your own errors. So this has the 3 advantages:
Also in your specific case you even have a bonus advantage:
|
@tyranron Does my suggestion not fix your usecase for |
@JelteF sorry for the quite long limbo state of the answer. 😱 First of all: And regarding the question itself: Why the alternatives, described above, won't play well for us:
Beside the our situation: enum DbError {
Postgres(postgres::Error),
MySql(mysql::Error),
} Here we do not want the That's why: |
@ffuugoo please, polish the PR and prepare to merge it without backtrace forwarding. |
Thanks for the response @tyranron (no worries on it taking a bit long). Looking forward to the updated PR without the
I think this would actually be a separate case where you would want to forward all Error methods with something like I think the reasons you describe why my solution doesn't work make quite a lot of sense and your usecase seems quite common. As far as I can tell the only real solution would be to have the If that PR is not accepted I indeed think it makes sense to add this functionality in this crate and I would accept a PR for for |
968f288
to
319bf30
Compare
@ffuugoo please, consider CI complains. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work! Especially all the tests you added. I left some small comments, on the code. Once you resolve those this can be merged.
This is now released in version 0.99.5 |
Implementation is mostly done, but there are 3 unresolved issues:
State
process attributes need some reorganization. Right now I've worked around it by sticking conditional on "deriving Error" right intoState
code.