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

Error when re-exporting bytemuck and using derive macros #93

Open
Rua opened this issue Mar 8, 2022 · 2 comments
Open

Error when re-exporting bytemuck and using derive macros #93

Rua opened this issue Mar 8, 2022 · 2 comments

Comments

@Rua
Copy link

Rua commented Mar 8, 2022

I re-export from one crate like this:

pub use bytemuck;

...and then import Zeroable and Pod from this re-exported location. Then I add

#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]

This triggers a compiler error:

error[E0433]: failed to resolve: could not find `bytemuck` in the list of imported crates
  --> examples/src/lib.rs:14:39
   |
14 | #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
   |                                       ^^^^^^^^ could not find `bytemuck` in the list of imported crates
   |
   = note: this error originates in the derive macro `Zeroable` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0433]: failed to resolve: could not find `bytemuck` in the list of imported crates
  --> examples/src/lib.rs:14:49
   |
14 | #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
   |                                                 ^^^ could not find `bytemuck` in the list of imported crates
   |
   = note: this error originates in the derive macro `Pod` (in Nightly builds, run with -Z macro-backtrace for more info)

When I add bytemuck as a dependency directly, and use Zeroable and Pod from there, the error goes away. It seems that the internals of the derive macro can't handle an alternative export path?

@Lokathor
Copy link
Owner

Lokathor commented Mar 9, 2022

https://github.com/Lokathor/bytemuck/blob/main/derive/src/traits.rs#L93 (and the similar methods on the other derives) seems to be the offender. They're always using ::bytemuck (with the absolute prefix) rather than bytemuck (no prefix).

I'm not super familiar with the derive code, but given that it's deriving unsafe traits i think the extra caution in the import path is probably appropriate.

If someone can submit a PR to fix this without causing potential problems with other traits under the same name ending up derivable and leading to strange bugs I'm all for that. Otherwise I might just say that in this particular case people should just use the full import path to work around this problem. Safety beats ergonomics in this case.

@danielhenrymantilla
Copy link
Contributor

danielhenrymantilla commented Mar 10, 2022

This is a known hard problem for proc-macros. I've been recently re-thinking about this, and here is the least-bad solution I can think of:

  1. Make ::bytemuck export a hidden-ish new proc-macro derive, say ZeroableUnqualified, which acts like Zeroable but for not prefixing bytemuck with the :: disambiguator.

  2. Then any dependency that wishes to re-export Zeroable, such as the OP's, can then do:

    pub mod bytemuck {
        pub use ::bytemuck::{*, ZeroableUnqualified as Zeroable};
    }
  3. A 2nd-degree dependency can then do:

    pub use ::middle_crate::bytemuck;
    
    #[derive(, bytemuck::Zeroable)]
    struct Foo

    or

    pub use ::middle_crate::bytemuck::{self, Zeroable};
    
    #[derive(, Zeroable)]
    struct Foo

Another approach, more classic but imho more cumbersome for the 2nd-degree dependency, would be for Zeroable to take an optional #[zeroable(crate = …)] parameter with which to override the currently hard-coded ::bytemuck.

This could even palliate a bit better the unsafe-ty @Lokathor was legitimately concerned about1: it's easy to change #[zeroable(crate = …)] to #[zeroable(unsafe { crate = … })], for instance.

At that point the ::middle_crate can keep using pub use ::bytemuck;, as usual, and the 2nd-degree dependency would have to write:

pub use ::middle_crate::bytemuck::{self, Zeroable};

#[derive(, Zeroable)]
#[zeroable(unsafe { crate = bytemuck })]
// or
#[zeroable(unsafe { crate = ::middle_crate::bytemuck })]
struct Foo
  • A variant of this approach, if ::middle_crate is motivated enough, would be for them to re-export their own Zeroable derive, one which would emit:

    #[::middle_crate::__internals::nested_derive(::middle_crate::bytemuck::Zeroable)]
    #[zeroable(unsafe { crate = ::middle_crate::bytemuck })]
    #input
    • where nested_derive is a helper proc-macro I'll be releasing for use cases such as this one 😅

    • A variant of this variant would be to use #[macro_rules_derive], so as to "just" write, from within ::middle_crate a macro_rules! definition:

      #[macro_export]
      macro_rules! Zeroable {( $input:item ) => (
          #[$crate::__internals::nested_derive($crate::bytemuck::Zeroable)]
          #[zeroable(unsafe { crate = $crate::bytemuck })]
          $input
      )}

      so that the 2nd-degree dependency could write:

      use ::middle_crate::prelude::{derive, Zeroable};
      
      #[derive(Zeroable!)]
      struct Foo
      • where derive would then be a re-export of a slightly tweaked version of #[macro_rules_derive] (which I'd also have to publish, but it would be quick)

Footnotes

  1. that being said, the story of macros and unsafety is known to be quite disappointing; the current realistic status quo is that the macro callsites are expected not to go to crazy with renamings and shadowings

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

No branches or pull requests

3 participants