diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cac3d601a..aa6073cc6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -523,6 +523,10 @@ jobs: "stable", "nightly", ] + features: [ + "", + "--features zerocopy_0_8", + ] steps: - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Configure environment variables @@ -540,19 +544,19 @@ jobs: toolchain: ${{ env.ZC_TOOLCHAIN }} components: clippy, rust-src - name: Check - run: ./cargo.sh +${{ matrix.toolchain }} check --package unsafe-fields --verbose + run: ./cargo.sh +${{ matrix.toolchain }} check --package unsafe-fields ${{ matrix.features }} --verbose - name: Check tests - run: ./cargo.sh +${{ matrix.toolchain }} check --tests --package unsafe-fields --verbose + run: ./cargo.sh +${{ matrix.toolchain }} check --tests --package unsafe-fields ${{ matrix.features }} --verbose - name: Build - run: ./cargo.sh +${{ matrix.toolchain }} build --package unsafe-fields --verbose + run: ./cargo.sh +${{ matrix.toolchain }} build --package unsafe-fields ${{ matrix.features }} --verbose - name: Run tests - run: ./cargo.sh +${{ matrix.toolchain }} test --package unsafe-fields --verbose + run: ./cargo.sh +${{ matrix.toolchain }} test --package unsafe-fields ${{ matrix.features }} --verbose - name: Clippy - run: ./cargo.sh +${{ matrix.toolchain }} clippy --package unsafe-fields --verbose + run: ./cargo.sh +${{ matrix.toolchain }} clippy --package unsafe-fields ${{ matrix.features }} --verbose # See comment in next step for why we only run on nightly. if: matrix.toolchain == 'nightly' - name: Clippy tests - run: ./cargo.sh +${{ matrix.toolchain }} clippy --package unsafe-fields --tests --verbose + run: ./cargo.sh +${{ matrix.toolchain }} clippy --package unsafe-fields --tests ${{ matrix.features }} --verbose # Clippy improves the accuracy of lints over time, and fixes bugs. Only # running Clippy on nightly allows us to avoid having to write code # which is compatible with older versions of Clippy, which sometimes @@ -573,7 +577,7 @@ jobs: METADATA_DOCS_RS_RUSTDOC_ARGS="$(cargo metadata --format-version 1 | \ jq -r ".packages[] | select(.name == \"unsafe-fields\").metadata.docs.rs.\"rustdoc-args\"[]" | tr '\n' ' ')" export RUSTDOCFLAGS="${{ matrix.toolchain == 'nightly' && '-Z unstable-options --document-hidden-items $METADATA_DOCS_RS_RUSTDOC_ARGS'|| '' }} $RUSTDOCFLAGS" - ./cargo.sh +${{ matrix.toolchain }} doc --document-private-items --package unsafe-fields + ./cargo.sh +${{ matrix.toolchain }} doc --document-private-items --package unsafe-fields --all-features # NEON intrinsics are currently broken on big-endian platforms. [1] This test ensures # that we don't accidentally attempt to compile these intrinsics on such platforms. We diff --git a/Cargo.toml b/Cargo.toml index 9f51d08eb0..d408ab4a03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ # https://github.com/dtolnay/trybuild/issues/207#issuecomment-131227.594 [workspace] +members = ["unsafe-fields", "zerocopy-derive"] + [workspace.package] # Inherited by zerocopy and unsafe-fields. rust-version = "1.65.0" @@ -85,7 +87,6 @@ testutil = { path = "testutil" } # sometimes change the output format slightly, so a version mismatch can cause # CI test failures. trybuild = { version = "=1.0.90", features = ["diff"] } -unsafe-fields = { path = "./unsafe-fields" } # In tests, unlike in production, zerocopy-derive is not optional zerocopy-derive = { version = "=0.9.0-alpha.0", path = "zerocopy-derive" } # TODO(#381) Remove this dependency once we have our own layout gadgets. diff --git a/unsafe-fields/Cargo.toml b/unsafe-fields/Cargo.toml index 9aba90fe3d..adc98bbff5 100644 --- a/unsafe-fields/Cargo.toml +++ b/unsafe-fields/Cargo.toml @@ -8,7 +8,7 @@ [package] name = "unsafe-fields" -version = "0.1.0" +version = "0.2.0" edition = "2021" description = "Make it unsafe to access or modify fields with safety invariants" license = "BSD-2-Clause OR Apache-2.0 OR MIT" @@ -20,3 +20,9 @@ exclude = [".*"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"] + + [lints.rust] + unexpected_cfgs = { level = "allow", check-cfg = ['cfg(doc_cfg)'] } + +[dependencies] +zerocopy_0_8 = { package = "zerocopy", path = "..", optional = true } diff --git a/unsafe-fields/src/lib.rs b/unsafe-fields/src/lib.rs index 38ee867b79..d782160d85 100644 --- a/unsafe-fields/src/lib.rs +++ b/unsafe-fields/src/lib.rs @@ -13,7 +13,8 @@ //! the [`Unsafe`] wrapper type. An `Unsafe` is intended to be used to for //! struct, enum, or union fields which carry safety invariants. All accessors //! are `unsafe`, which requires any use of an `Unsafe` field to be inside an -//! `unsafe` block. +//! `unsafe` block. One exception is [`Unsafe::as_ref`], which is available when +//! the `zerocopy_0_8` feature is enabled. See its docs for more information. //! //! An unsafe field has the type `Unsafe`. `O` is //! the enclosing type (struct, enum, or union), `F` is the type of the field, @@ -23,6 +24,8 @@ //! the same enclosing type. Note that swapping the same field between instances //! of the same type [cannot be prevented](crate#limitations). //! +//! [immutable]: zerocopy_0_8::Immutable +//! //! # Examples //! //! ``` @@ -46,7 +49,7 @@ //! return None; //! } //! // SAFETY: We just confirmed that `n` is even. -//! let n = unsafe { Unsafe::new(n) }; +//! let n = unsafe { Unsafe::new_unchecked(n) }; //! Some(EvenUsize { n }) //! } //! } @@ -170,6 +173,7 @@ rustdoc::missing_crate_level_docs, rustdoc::private_intra_doc_links )] +#![cfg_attr(doc_cfg, feature(doc_cfg))] use core::marker::PhantomData; @@ -210,16 +214,41 @@ impl Clone for Unsafe Unsafe { /// Gets a reference to the inner value. /// + /// If [`F: Immutable`][immutable], prefer [`as_ref`], which is safe. + /// + /// [immutable]: zerocopy_0_8::Immutable + /// [`as_ref`]: Unsafe::as_ref + /// /// # Safety /// /// The caller is responsible for upholding any safety invariants associated /// with this field. #[inline(always)] - pub const unsafe fn as_ref(&self) -> &F { + pub const unsafe fn as_ref_unchecked(&self) -> &F { // SAFETY: This method is unsafe to call. &self.field } + /// Gets a reference to the inner value safely so long as the inner value is + /// immutable. + /// + /// If [`F: Immutable`][immutable], then `F` does not permit interior + /// mutation, and so it is safe to return a reference to it. + /// + /// [immutable]: zerocopy_0_8::Immutable + #[inline(always)] + #[cfg(feature = "zerocopy_0_8")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "zerocopy_0_8")))] + pub const fn as_ref(&self) -> &F + where + F: zerocopy_0_8::Immutable, + { + // SAFETY: `F: Immutable` guarantees that the returned `&F` cannot be + // used to mutate `self`, and so it cannot be used to violate any + // invariant. + unsafe { self.as_ref_unchecked() } + } + /// Gets a mutable reference to the inner value. /// /// # Safety @@ -234,7 +263,7 @@ impl Unsafe { } impl Unsafe { - /// Constructs a new `Unsafe`. + /// Constructs a new `Unsafe`. /// /// # Safety /// @@ -247,13 +276,8 @@ impl Unsafe { } /// Extracts the inner `F` from `self`. - /// - /// # Safety - /// - /// The caller is responsible for upholding any safety invariants associated - /// with this field. #[inline(always)] - pub const unsafe fn into(self) -> F { + pub const fn into(self) -> F { use core::mem::ManuallyDrop; let slf = ManuallyDrop::new(self); @@ -280,8 +304,6 @@ impl Unsafe { // validity as `T` let dst = unsafe { Transmute { src: slf }.dst }; - // SAFETY (satisfaction of `Unsafe`'s field invariant): This method is - // unsafe to call. ManuallyDrop::into_inner(dst) } } @@ -365,8 +387,9 @@ mod tests { #[test] #[allow(clippy::undocumented_unsafe_blocks)] fn test_unsafe_fieds() { - let mut _foo = Foo { a: unsafe { Unsafe::new(0) }, b: 0 }; - let mut _bar = Bar { a: unsafe { Unsafe::new(0) }, b: unsafe { Unsafe::new(0) } }; + let mut _foo = Foo { a: unsafe { Unsafe::new_unchecked(0) }, b: 0 }; + let mut _bar = + Bar { a: unsafe { Unsafe::new_unchecked(0) }, b: unsafe { Unsafe::new_unchecked(0) } }; } }