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

Support for multi-versioned public use of zerocopy #619

Closed
jswrenn opened this issue Nov 15, 2023 · 1 comment
Closed

Support for multi-versioned public use of zerocopy #619

jswrenn opened this issue Nov 15, 2023 · 1 comment

Comments

@jswrenn
Copy link
Collaborator

jswrenn commented Nov 15, 2023

There are a number of high-profile crates in the Rust ecosystem that define types whose memory representation is part of their documented contract. By implementing zerocopy's traits for these types, their users could benefit from zerocopy's compiler-time checks that operations depending on these contracts are sound. But: Where should these trait implementations go?

It is risky for zerocopy to provide trait implementations on foreign types, since zerocopy cannot check the soundness of its implementations on foreign types. If zerocopy's trait implementations implementations on foreign types fell out of sync with the definitions of those types, unsoundness would ensue.

It is strictly safer for third-party types to provide their own implementations of zerocopy's traits, using zerocopy's derive feature. However, zerocopy is still a crate in active development, with occasional breaking changes. For greater interoperability in the ecosystem, we would like our public customers to track zerocopy's latest version. To do so, customers today must either:

  1. document their zerocopy support as unstable
  2. be willing to release new breaking changes of their crate, in step with zerocopy

Both of these options adds friction to the adoption of zerocopy in the Rust ecosystem.

Proposed Solution

There exists a third not-yet-quite-possible-today option: Provided multi-versioned support for zerocopy.

A customer taking this approach would specify a new optional dependency for each version of zerocopy they support; e.g., they would start with:

[dependencies]
zerocopy_0_7 = {  package = "zerocopy", version = "0.7", optional = true, features = ["derive"] }

...and provide feature-gated implementations for that version:

#[cfg_attr(
    feature = "zerocopy_0_7",
    derive(
        zerocopy_0_7::FromZeroes,
        zerocopy_0_7::FromBytes,
        zerocopy_0_7::AsBytes
    ),
    zerocopy_0_7(root = ::zerocopy_0_7),
)]
#[repr(C)]
pub struct sembuf {
    pub sem_num: c_ushort,
    pub sem_op: c_short,
    pub sem_flg: c_short,
}

Upon a new major release of zerocopy, the customer could append a new version to their Cargo.toml:

  [dependencies]
  zerocopy_0_7 = {  package = "zerocopy", version = "0.7", optional = true, features = ["derive"] }
+ zerocopy_0_8 = {  package = "zerocopy", version = "0.8", optional = true, features = ["derive"] }

...and add new, corresponding feature-gated derives:

  #[cfg_attr(
      feature = "zerocopy_0_7",
      derive(
          zerocopy_0_7::FromZeroes,
          zerocopy_0_7::FromBytes,
          zerocopy_0_7::AsBytes
      ),
  )]
+ #[cfg_attr(
+     feature = "zerocopy_0_8",
+     derive(
+         zerocopy_0_8::KnownLayout,
+         zerocopy_0_8::TryFromBytes,
+         zerocopy_0_8::FromZeroes,
+         zerocopy_0_8::FromBytes,
+         zerocopy_0_8::AsBytes
+     ),
+ )]
  #[repr(C)]
  pub struct sembuf {
      pub sem_num: c_ushort,
      pub sem_op: c_short,
      pub sem_flg: c_short,
  }

Technical Details

Presently, zerocopy's derives unconditionally expand to code referencing the path ::zerocopy. This makes depending on multiple versions of zerocopy presently impossible, since they cannot all share that path. To support this approach, zerocopy can make one of two technical changes:

1. Root-setting helper attribute.

Zerocopy could provide a helper attribute for setting its crate root; e.g.:

#[cfg_attr(
    feature = "zerocopy_0_7",
    derive(
        zerocopy_0_7::FromZeroes,
        zerocopy_0_7::FromBytes,
        zerocopy_0_7::AsBytes
    ),
    zerocopy_0_7(crate = ::zerocopy_0_7), // <--- this
)]

The attribute would instruct zerocopy's derives to expand to paths beginning with ::zerocopy_0_7, instead of ::zerocopy.

The drawback of this approach is that this attribute adds line noise to critical derives.

2. Re-export old versions

Alternatively, zerocopy could optionally depend on older versions of itself:

[dependencies]
# ...
v_0_6 = {  package = "zerocopy", version = "0.6", optional = true }
# ...

...and provide versioned re-exports of itself at its root:

#[doc(hidden)]
mod version {
    pub use super:: as v0_7;

    #[cfg(feature = "v_0_6")]
    pub use ::v_0_6;
}

We would then modify zerocopy's derives to always expand to paths beginning with ::zerocopy::version::v_XXX.

Unresolved question: In this approach, how would we populate the zerocopy path on the consumer side?

@joshlf
Copy link
Member

joshlf commented Oct 10, 2024

Merged into #11 (comment)

@joshlf joshlf closed this as completed Oct 10, 2024
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

2 participants