Skip to content
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

core::error, no_std #304

Closed
wants to merge 27 commits into from
Closed

core::error, no_std #304

wants to merge 27 commits into from

Conversation

jordens
Copy link
Contributor

@jordens jordens commented Jun 14, 2024

error_in_core has been stabilized in 1.81. This PR builds on the work of #64/#211/#244.

  • Introduce a std feature (default enabled) to gate the std components (currently only std::path)
  • Private re-export of std::error or core::error to avoid bumping MSRV for std. no_std does require 1.81.
  • Tests/docs/CI updates
  • Macro hygiene: impl/expand.rs uses full absolute paths.

It's unclear to me whether it's better to bump the MSRV (1.56 -> 1.81) or use a private re-export of either std::error or core::error (as done here) to keep support for rustc < 1.81.

@jstarks
Copy link

jstarks commented Jun 14, 2024

Given the wide use of this crate, it seems very unlikely that an MSRV bump would be accepted.

@jordens
Copy link
Contributor Author

jordens commented Jul 2, 2024

@dtolnay what's your take on the PR, especially regarding the MSRV question?

It's unclear to me whether it's better to bump the MSRV (1.56 -> 1.81 as done in this draft) or use a private re-export of either std::error or core::error (as done previously) to keep support for rustc < 1.81.

@umgefahren
Copy link

What if one is just able to somehow pass core::error instead of std::error?

* upstream/master:
  Upload CI Cargo.lock for reproducing failures
  Work around new dead code warning in test
  Release 1.0.63
  Update documentation of #[from] and #[backtrace] attributes
  Release 1.0.62
  Support .0.0 nested tuple index
  Add regression test for issue 309
  No need for dead code if struct fields are public
  Ignore warning on unused struct in test
  Fill in ignore reasons in all #[ignore] attributes
@Systemcluster Systemcluster mentioned this pull request Sep 5, 2024
@kayabaNerve
Copy link

My personal belief is default-features = false should be MSRV 1.81 and features = ["std"] should be MSRV 1.56.

@jordens jordens marked this pull request as ready for review September 5, 2024 21:03
@jordens
Copy link
Contributor Author

jordens commented Sep 6, 2024

@dtolnay any guidance on how you'd like to see it done?

This reverts commit 8cbdc4e.
* let integration tests require `std` where necessary
* don't use error_in_core anymore
don't warn if more than one of the `ignore` conditions hold
@kayabaNerve
Copy link

PR LGTM with edge case that I don't believe you can import thiserror with a distinct package name with this path (I'm unsure if you could already).

@jordens jordens changed the title std::error -> core::error, no_std core::error, no_std Sep 6, 2024
@dhedey
Copy link

dhedey commented Sep 24, 2024

@jordens - What do we think about using build.rs to effectively force the use of std::core functionality on rustc < 1.81 ?

We could do this by introducing a cargo:rustc-cfg=thiserror_no_core_error rustc-cfg flag, in a very similar manner to dtolnay did in his equivalent PR in anyhow https://github.com/dtolnay/anyhow/pull/383/files ?

* upstream/master:
  Release 1.0.64
  Mark #[automatically_derived] for generated impls
@jordens
Copy link
Contributor Author

jordens commented Sep 26, 2024

I'd like to hear about the prospects of this PR first.

JayWhite2357 pushed a commit to spaceandtimelabs/sxt-proof-of-sql that referenced this pull request Sep 27, 2024
# Rationale for this change

In order to make this rep `no_std` compatible, we need `no_std` compatible errors. `thiserror` is not `no_std` compatible, while `snafu` is. We are currently using `thiserror`.

Note: While dtolnay/thiserror#304 is an open PR, it is not clear how soon this will get merged. Additionally, Substrate uses `snafu`, which would help unify dependencies for Substrate-based integrations.

# What changes are included in this PR?
NOTE: the individual commits may be good to review individually.
- All error enum variants consisting of tuple structs are transformed
into named structs. This is necessary because `snafu` does not support
tuple structs.
- Every `#[derive(Error)]` is substituted with the analogous
`#[derive(Snafu)]`. In particular:
* `#[error(...)]` attributes are substituted with equivalent
`#[snafu(display(...))]` attributes
* `#[error(transparent)]` attributes are substituted with equivalent
`#[snafu(transparent)]` attributes (which also derive the corresponding
`From` implementation)
* For `ConversionError::TimestampConversionError`, the
`#[snafu(context(false), display(...))]` attribute is used for deriving
a `From` implementation, and at the same time maintain the custom error
message
- A `std` feature is introduced for the `proof-of-sql` crate, which in
turns activates the `snafu/std` feature. The `std` feature is required
for the `posql_db` example, because `Clap` relies on the
`std::error::Error` trait.
- `thiserror` still appears in the dependency tree (`cargo tree -i
thiserror`), but only as a transitive dependency (via `blitzar`, and
dev-dependencies)

# Are these changes tested?
Yes. This PR is a refactoring, and all existing tests pass.
Copy link

@bushrat011899 bushrat011899 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really like this to land; the implementation looks good and even manages to avoid bumping MSRV as long as you include the std feature. Getting this merged would unblock some no_std support for Bevy that I'd love to get moving before the end of the year if possible.

Copy link

@BenjaminBrienen BenjaminBrienen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good change which addresses all concerns and merging this would enable other crates to continue using thiserror. It's unfortunate that the ecosystem is splitting over this problem which is already solved.

@dhedey
Copy link

dhedey commented Oct 10, 2024

I certainly support this PR.

That said, and please correct me if I’m wrong, but it is my understanding that if we release this current PR, we immediately break the builds of anyone compiling with rustc < 1.81 which imports thiserror with default-features = false.

Given the widespread usage of thiserror, I would imagine such a breakage is not tenable.

I have suggested a simple tweak to this PR which I believe avoids this issue in this comment by effectively always enabling an equivalent-to-std feature before the Error trait is available in core.

I assume given dtolnay was the author of the equivalent PR I linked to in anyhow, they would favour such a solution, and given that I assume the merging of any such PR is blocked on dtolnay’s approval, it seems likely we require such a change; tested with this particular import case.

But maybe I’m missing something which means thiserror should be given different treatment?

@jstarks
Copy link

jstarks commented Oct 10, 2024

I certainly support this PR.

That said, and please correct me if I’m wrong, but it is my understanding that if we release this current PR, we immediately break the builds of anyone compiling with rustc < 1.81 which imports thiserror with default-features = false.

Given the widespread usage of thiserror, I would imagine such a breakage is not tenable.

I have suggested a simple tweak to this PR which I believe avoids this issue in this comment by effectively always enabling an equivalent-to-std feature before the Error trait is available in core.

I assume given dtolnay was the author of the equivalent PR I linked to in anyhow, they would favour such a solution, and given that I assume the merging of any such PR is blocked on dtolnay’s approval, it seems likely we require such a change; tested with this particular import case.

But maybe I’m missing something which means thiserror should be given different treatment?

There are a few places where std::path::Path is referenced, as part of an impl on some trait. Path is not and will never be in core. So these references have to be gated behind a feature.

@dhedey
Copy link

dhedey commented Oct 11, 2024

There are a few places where std::path::Path is referenced, as part of an impl on some trait. Path is not and will never be in core. So these references have to be gated behind a feature.

I'm not suggesting we remove the std feature - I'm just suggesting it's effectively auto-enabled pre rust 1.81, to minimize compilation/CI breaks for people using a rust version before it supports core::error.

The current PR I believe will break compilation for anyone who is building on rustc < 81 and has imported thiserror with default-features = false.

Instead, I suggest similar in execution to this PR on anyhow we:

  • Introduce in build.rs, if rustc < 81 then println!("cargo:rustc-cfg=thiserror_no_core_error_force_use_std"); (and an equivalent line to avoid lint errors in rustc >= 81)
  • In code, replace feature = "std" with any(feature = "std", thiserror_no_core_error_force_use_std),

After this change, I believe the breaks are minimized to people who:

  • Are on rustc >= 81
  • Use default-features = false
  • Rely on std features in this crate

Which I imagine is a sufficiently small crowd that this might be deemed acceptable, given the fix is to just add the std feature. It would be even nicer if we could detect such breakages, and advise in the compilation error that they need to use the std feature.

For what it's worth, I think this is particularly a problem because thiserror can be used by libraries rather than applications, and so may affect library consumers who don't have a direct thiserror dependency themselves; but just weren't using a lock file or started a fresh project with a slightly outdated rust version. Such users may be less experienced, and may find it frustrating to have to fix a compilation error due to an indirect dependency breaking.

Copy link

@dhedey dhedey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few thoughts/suggestions from a bystander.

@@ -37,7 +37,7 @@ fn fallback(input: &DeriveInput, error: syn::Error) -> TokenStream {

#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics std::error::Error for #ty #ty_generics #where_clause
impl #impl_generics ::thiserror::__private::error::Error for #ty #ty_generics #where_clause
Copy link

@dhedey dhedey Oct 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest, why did we choose this rather than hardcoding ::std or ::core, in the compiled output (via e.g. an #error_crate variable)?

My slight concern is that use of ::thiserror::__private may leak this __private namespace out and could confuse people debugging this. But using ::std / ::core directly it's more obvious what's failing.

@@ -307,12 +309,12 @@ fn impl_enum(input: Enum) -> TokenStream {
let self_provide = if type_is_option(backtrace_field.ty) {
quote! {
if let ::core::option::Option::Some(backtrace) = backtrace {
#request.provide_ref::<std::backtrace::Backtrace>(backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
Copy link

@dhedey dhedey Oct 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR: It may be nice if we gave a better compiler error if a user tried to specify a backtrace when we're not in std mode, which could advise the user to enable the std feature to use this? I believe the right place for this would be in valid.rs.

... that said, it might actually cause more breakage if people are using default-features = false mode and after upgrading they get a compilation error from this "help" because they are actually running in std mode, just importing this crate incorrectly. So I think I've talked myself out of this.

So maybe safer to leave this as-is, and perhaps document that this only works if compiling with std available?

@@ -11,6 +11,8 @@ license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/thiserror"
rust-version = "1.56"

# without the `std` feature (i.e. `no_std`): rust-version = "1.81"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in this comment, I feel we should attempt to widen our build compatibility to not break rustc < 1.81 builds importing thiserror with default-features = false.

@dhedey
Copy link

dhedey commented Oct 11, 2024

Actually, I'm a little wrong, apologies. I think I misinterpreted the PR on anyhow.

The PR on anyhow simply avoids outputting a core error at all if anyhow_no_core_error is true.

So, when upgrading to anyhow = { version = "1.0.89", default-features = false } with rustc at 1.80, it appears the crate itself compiles, but downstream crates using the auto-conversions are broken because it no longer outputs conversions from std::error::Error to anyhow::Error:

fn main() -> Result<()> {
    let config = std::fs::read_to_string("cluster.json")?;
    Ok(())
}
Compilation Error

error[E0277]: `?` couldn't convert the error to `anyhow::Error`
 --> src/main.rs:4:57
  |
3 | fn main() -> Result<()> {
  |              ---------- expected `anyhow::Error` because of this
4 |     let config = std::fs::read_to_string("cluster.json")?;
  |                  ---------------------------------------^ the trait `From<std::io::Error>` is not implemented for `anyhow::Error`, which is required by `Result<(), anyhow::Error>: FromResidual<Result<Infallible, std::io::Error>>`
  |                  |
  |                  this can't be annotated with `?` because it has type `Result<_, std::io::Error>`
  |
  = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
  = help: the following other types implement trait `FromResidual<R>`:
            <Result<T, F> as FromResidual<Result<Infallible, E>>>
            <Result<T, F> as FromResidual<Yeet<E>>>
  = note: required for `Result<(), anyhow::Error>` to implement `FromResidual<Result<Infallible, std::io::Error>>`

That said, unlike anyhow, this crate (and indeed the output from the macro) simply wouldn't compile on 1.80 with default-features = false, so I think personally I'd still advocate for effectively force-enabling the std feature on older rustc.

But I accept others may disagree, and I don't think it's a blocker.

Maybe I'm strange to default to using default-features = false for all imports and only turning on what I need 👍 .

@jdygert-spok
Copy link

To be clear, anyhow already had an std feature enabled by default, meaning that PR strictly added functionality when core::error::Error is available. thiserror does NOT currently have an std feature, meaning this is a breaking change, depending on how you view default features.

@kayabaNerve
Copy link

I think all of this can be argued bikeshedding and is best left to a further PR. Someone can even now prepare a PR, descending from these commits, ready to be merged after this PR is merged if it's acceptably done and desirable. It doesn't have to be a blocker here.

@domenukk
Copy link

As another reference point, rust-url - with a similar amount of users on crates.io - just accepted a PR that adds a std feature. Just as in this PR, MSRV for std stayed the same, no_std requires 1.81.
servo/rust-url#831

TL;DR: Would be great to get this PR merged as well :)

@clabby
Copy link

clabby commented Oct 19, 2024

Any updates here? This PR looks good to go, other popular crates have adopted this same pattern. @dtolnay, any notes on what's blocking this on your end?

@juliankrieger
Copy link

Is there any work left to do here? I would love to help.

@zdivelbiss
Copy link

I'd love to use this crate in a project of mine, and it seems there hasn't been much activity in describing what needs to be done / changed.

@dtolnay Any input on how this can proceed to being merged?

Copy link
Owner

@dtolnay dtolnay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a chance to work on this feature this week, and I ended up going with a different implementation that automatically enables a std dependency in compilers older than 1.81, more like my corresponding change in anyhow from earlier which has been linked above, and along the lines of what was suggested by @dhedey in #304 (comment) and #304 (comment). There were also some future compatibility drawbacks of this PR that I handled differently, which you can find in #373 and #372.

Thanks anyway for the work on this PR, and also to @dhedey for numerous substantive comments.

@dtolnay dtolnay closed this Nov 6, 2024
@jordens jordens deleted the no-std branch November 6, 2024 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.