Move transparent_newtype to use include!
What changed, and why it matters
This commit is a routine internal refactoring of the rust-bitcoin project. It moves a helper macro called transparent_newtype from one internal crate to a shared include file, then updates all crates to use the shared version. There is no change to user-facing behavior, no bug fix, and no security relevance.
No security action required. Treat as normal maintenance/refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change copies the transparent_newtype macro (and its supporting macros) from internals/src/macros.rs into a new include/newtype.rs file. Each crate that previously used internals::transparent_newtype now includes the file via include!(“../../include/newtype.rs”) and calls the macro locally (crate::transparent_newtype! or transparent_newtype!). The old macro in internals is marked #[deprecated] but remains for compatibility. The macro’s implementation is unchanged; only its source location and invocation path differ. Some call sites also add a doc comment link to an example file.
Changed components
internals/src/macros.rsinclude/newtype.rsbitcoin/src/lib.rshashes/src/lib.rsio/src/lib.rsprimitives/src/lib.rsbitcoin/src/address/mod.rsbitcoin/src/blockdata/script/push_bytes.rsbitcoin/src/blockdata/transaction.rsbitcoin/src/crypto/key.rsbitcoin/src/taproot/merkle_branch/borrowed.rshashes/src/internal_macros.rshashes/src/muhash/mod.rshashes/src/sha256t/mod.rsio/src/bridge.rsprimitives/src/script/borrowed.rsInspect captured patch +229 / −11
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index 76125e05..de1bfb7a 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -308,7 +308,8 @@ pub enum AddressData {
},
}
-internals::transparent_newtype! {
+// Defined in `REPO_DIR/include/newtype.rs`.
+transparent_newtype! {
/// A Bitcoin address.
///
/// # Parsing addresses
diff --git a/bitcoin/src/blockdata/script/push_bytes.rs b/bitcoin/src/blockdata/script/push_bytes.rs
index 23e93bbd..572edd62 100644
--- a/bitcoin/src/blockdata/script/push_bytes.rs
+++ b/bitcoin/src/blockdata/script/push_bytes.rs
@@ -37,7 +37,8 @@ mod primitive {
}
}
- internals::transparent_newtype! {
+ // Defined in `REPO_DIR/include/newtype.rs`.
+ transparent_newtype! {
/// Byte slices that can be in Bitcoin script.
///
/// The encoding of Bitcoin script restricts data pushes to be less than 2^32 bytes long.
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 50d73807..4fe9097a 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -1192,7 +1192,8 @@ impl InputWeightPrediction {
}
}
-internals::transparent_newtype! {
+// Defined in `REPO_DIR/include/newtype.rs`.
+transparent_newtype! {
/// A wrapper type for the coinbase transaction of a block.
///
/// This type exists to distinguish coinbase transactions from regular ones at the type level.
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 13b4ed63..565d33c8 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -245,7 +245,7 @@ mod encapsulate {
pub fn as_keypair(&self) -> &Keypair { &self.0 }
}
- internals::transparent_newtype! {
+ transparent_newtype! {
/// An array of bytes that's semantically an x-only public but was **not** validated.
///
/// This can be useful when validation is not desired but semantics of the bytes should be
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index a2ded73e..c6027f29 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -79,6 +79,8 @@ pub extern crate serde;
mod internal_macros;
+include!("../../include/newtype.rs"); // Explained in `REPO_DIR/docs/README.md`.
+
pub mod ext {
//! Re-export all the extension traits so downstream can use wildcard imports.
//!
diff --git a/bitcoin/src/taproot/merkle_branch/borrowed.rs b/bitcoin/src/taproot/merkle_branch/borrowed.rs
index dea3a5dd..6a069ff0 100644
--- a/bitcoin/src/taproot/merkle_branch/borrowed.rs
+++ b/bitcoin/src/taproot/merkle_branch/borrowed.rs
@@ -13,7 +13,8 @@ use super::{
mod privacy_boundary {
use super::*;
- internals::transparent_newtype! {
+ // Defined in `REPO_DIR/include/newtype.rs`.
+ transparent_newtype! {
/// The Merkle proof for inclusion of a tree in a Taproot tree hash.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TaprootMerkleBranch([TapNodeHash]);
diff --git a/docs/README.md b/docs/README.md
index 4f6c691d..feda9fbd 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -11,3 +11,10 @@ hold and a PR made to the `docs/` tree or in the Discussions section to debate i
## Dependency tree
The `./dep-tree` file was generated using `just gen-dep-tree`.
+
+## include! usage
+
+In this repository, the `include` directory holds shared source code that can be used in any of the
+crates through the `include!` macro. Generally, files in the `include` directory should be written
+such that their content can be included once at the top level of a crate (e.g. in `lib.rs`), and
+then used throughout, rather than calling `include!` at each usage site.
\ No newline at end of file
diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs
index dcbc4787..03ce6ba2 100644
--- a/hashes/src/internal_macros.rs
+++ b/hashes/src/internal_macros.rs
@@ -135,7 +135,8 @@ macro_rules! hash_type_no_default {
const DISPLAY_BACKWARD: bool = $reverse:expr;
) => {
- internals::transparent_newtype! {
+ // Defined in `REPO_DIR/include/newtype.rs`.
+ crate::transparent_newtype! {
$(#[$type_attrs])*
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Hash([u8; $len]);
diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs
index c2892c87..5be67ddb 100644
--- a/hashes/src/lib.rs
+++ b/hashes/src/lib.rs
@@ -306,6 +306,8 @@ pub fn debug_hex<'a>(
Ok(())
}
+include!("../../include/newtype.rs"); // Explained in `REPO_DIR/docs/README.md`.
+
#[cfg(test)]
mod tests {
use crate::sha256d;
diff --git a/hashes/src/muhash/mod.rs b/hashes/src/muhash/mod.rs
index 0e07abb6..f713d826 100644
--- a/hashes/src/muhash/mod.rs
+++ b/hashes/src/muhash/mod.rs
@@ -11,7 +11,7 @@
const BYTE_SIZE: usize = 384;
// This code is the exact same as calling `hash_type_no_default!` but excludes call to `impl_write`.
-internals::transparent_newtype! {
+crate::transparent_newtype! {
/// Output of the `MuHash3072` hash function.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Hash([u8; BYTE_SIZE]);
diff --git a/hashes/src/sha256t/mod.rs b/hashes/src/sha256t/mod.rs
index 303ec9a8..8b88e89f 100644
--- a/hashes/src/sha256t/mod.rs
+++ b/hashes/src/sha256t/mod.rs
@@ -43,7 +43,8 @@ pub trait Tag {
const MIDSTATE: sha256::Midstate;
}
-internals::transparent_newtype! {
+// Defined in `REPO_DIR/include/newtype.rs`.
+crate::transparent_newtype! {
/// Output of the `SHA256t` hash function.
pub struct Hash<T>(PhantomData<T>, [u8; 32]);
diff --git a/include/newtype.rs b/include/newtype.rs
new file mode 100644
index 00000000..feb66537
--- /dev/null
+++ b/include/newtype.rs
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: CC0-1.0
+
+/// Constructs a transparent wrapper around an inner type and soundly implements reference casts.
+///
+/// This macro takes care of several issues related to newtypes that need to allow casting their
+/// inner types to themselves:
+///
+/// * It makes sure to put repr(transparent) on the type
+/// * It optionally implements conversions from `&`, `&mut`, `Box`, `Rc`, `Arc`
+/// * It makes sure to put `#[inline]` on all of these conversions since they are trivial
+/// * It makes sure the reference cast is const
+/// * It makes sure the `Arc` conversion is conditioned on `target_has_atomic = "ptr"`
+///
+/// Usage: just type the struct inside the macro as you would implementing it manually except leave
+/// `#[repr(transparent)]` out. Then add an impl block for the just-defined type containing function
+/// declarations that take a reference/smart pointer to `_` (use literal underscore; e.g. `&_` for
+/// shared references) and return `Self` behind the appropriate "pointer" type. Do not write the
+/// body, just semicolon.
+///
+/// The `alloc` types MUST NOT have import paths and don't need imports.
+macro_rules! transparent_newtype {
+ (
+ $(#[$($struct_attr:tt)*])*
+ $vis:vis struct $newtype:tt$(<$gen:ident $(= $default:ty)?>)?($($fields:tt)+) $(where $($where_ty:ty: $bound:path),* $(,)?)?;
+
+ impl$(<$gen2:tt>)? $newtype2:ident$(<$gen3:tt>)? {
+ $(
+ $(#[$($fn_attr:tt)*])*
+ $fn_vis:vis $(const)? fn $fn:ident($fn_arg_name:ident: $($fn_arg_ty:tt)+) -> $fn_ret_ty:ty;
+ )*
+ }
+ ) => {
+ crate::_check_tts_eq!($newtype2, $newtype, "the type name in the impl block doesn't match the struct name");
+ $(
+ // WARNING: renaming has to be disabled for soundness!
+ // If it weren't it'd be possible to make the type inside struct not match the one passed
+ // to functions. In principle we could also omit the generics but that'd be confusing for
+ // readers.
+ crate::_check_tts_eq!($gen2, $gen, "the name of the left generic parameter in impl block doesn't match the one on struct");
+ crate::_check_tts_eq!($gen3, $gen, "the name of the right generic parameter in impl block doesn't match the one on struct");
+ )?
+ $(#[$($struct_attr)*])*
+ #[repr(transparent)]
+ $vis struct $newtype$(<$gen $(= $default)?>)?($($fields)+) $(where $($where_ty: $bound),*)?;
+
+ impl$(<$gen2>)? $newtype$(<$gen3>)? $(where $($where_ty: $bound),*)? {
+ crate::_transparent_ref_conversions! {
+ crate::_transparent_newtype_inner_type!($($fields)+);
+ $(
+ $(#[$($fn_attr)*])*
+ $fn_vis fn $fn($fn_arg_name: $($fn_arg_ty)+) -> $fn_ret_ty;
+ )+
+ }
+ }
+ };
+}
+#[allow(unused_imports)]
+pub(crate) use transparent_newtype;
+
+macro_rules! _transparent_ref_conversions {
+ (
+ $inner:ty;
+ $(
+ $(#[$($fn_attr:tt)*])*
+ $fn_vis:vis $(const)? fn $fn:ident($fn_arg_name:ident: $($fn_arg_ty:tt)+) -> $fn_ret_ty:ty;
+ )+
+ ) => {
+ $(
+ crate::_transparent_ref_conversion! {
+ $inner;
+ $(#[$($fn_attr)*])*
+ $fn_vis fn $fn($fn_arg_name: $($fn_arg_ty)+) -> $fn_ret_ty;
+ }
+ )+
+ }
+}
+pub(crate) use _transparent_ref_conversions;
+
+macro_rules! _transparent_ref_conversion {
+ (
+ $inner:ty;
+ $(#[$($from_ref_attr:tt)*])*
+ $from_ref_vis:vis fn $from_ref:ident($from_ref_arg_name:ident: &_) -> $fn_ret_ty:ty;
+ ) => {
+ #[inline]
+ $(#[$($from_ref_attr)*])*
+ $from_ref_vis const fn $from_ref($from_ref_arg_name: &$inner) -> &Self {
+ // SAFETY: the pointer is created by casting a pointer that is pointing to an object
+ // with the same layout and validity invariants and the previous pointer was created
+ // directly from a reference. (Notice repr(transparent).)
+ // The lifetime of the input reference matches the lifetime of the returned reference.
+ unsafe { &*($from_ref_arg_name as *const $inner as *const Self) }
+ }
+ };
+ (
+ $inner:ty;
+ $(#[$($from_mut_attr:tt)*])*
+ $from_mut_vis:vis fn $from_mut:ident($from_mut_arg_name:ident: &mut _) -> $fn_ret_ty:ty;
+ ) => {
+ #[inline]
+ $(#[$($from_mut_attr)*])*
+ $from_mut_vis fn $from_mut($from_mut_arg_name: &mut $inner) -> &mut Self {
+ // SAFETY: the pointer is created by casting a pointer that is pointing to an object
+ // with the same layout and validity invariants and the previous pointer was created
+ // directly from a reference. (Notice repr(transparent).)
+ // The lifetime of the input reference matches the lifetime of the returned reference.
+ unsafe { &mut *($from_mut_arg_name as *mut $inner as *mut Self) }
+ }
+ };
+ (
+ $inner:ty;
+ $(#[$($from_box_attr:tt)*])*
+ $from_box_vis:vis fn $from_box:ident($from_box_arg_name:ident: Box<_>) -> $fn_ret_ty:ty;
+ ) => {
+ internals::_emit_alloc! {
+ $(#[$($from_box_attr)*])*
+ #[inline]
+ #[allow(clippy::ptr_as_ptr)] // Cannot use `cast()` because size of `Self` is not know at compile time.
+ $from_box_vis fn $from_box($from_box_arg_name: alloc::boxed::Box<$inner>) -> alloc::boxed::Box<Self> {
+ let ptr = alloc::boxed::Box::into_raw($from_box_arg_name);
+ // SAFETY: the pointer is created by casting a pointer that is pointing to an object
+ // with the same layout and validity invariants and the previous pointer was created
+ // directly from box. (Notice repr(transparent).)
+ unsafe { alloc::boxed::Box::from_raw(ptr as *mut Self) }
+ }
+ }
+ };
+
+ (
+ $inner:ty;
+ $(#[$($from_rc_attr:tt)*])*
+ $from_rc_vis:vis fn $from_rc:ident($from_rc_arg_name:ident: Rc<_>) -> $fn_ret_ty:ty;
+ ) => {
+ internals::_emit_alloc! {
+ $(#[$($from_rc_attr)*])*
+ #[inline]
+ $from_rc_vis fn $from_rc($from_rc_arg_name: alloc::rc::Rc<$inner>) -> alloc::rc::Rc<Self> {
+ let ptr = alloc::rc::Rc::into_raw($from_rc_arg_name);
+ // SAFETY: the pointer is created by casting a pointer that is pointing to an object
+ // with the same layout and validity invariants and the previous pointer was created
+ // directly from box. (Notice repr(transparent).)
+ unsafe { alloc::rc::Rc::from_raw(ptr as *mut Self) }
+ }
+ }
+ };
+
+ (
+ $inner:ty;
+ $(#[$($from_arc_attr:tt)*])*
+ $from_arc_vis:vis fn $from_arc:ident($from_arc_arg_name:ident: Arc<_>) -> $fn_ret_ty:ty;
+ ) => {
+ internals::_emit_alloc! {
+ $(#[$($from_arc_attr)*])*
+ #[cfg(target_has_atomic = "ptr")]
+ #[inline]
+ $from_arc_vis fn $from_arc($from_arc_arg_name: alloc::sync::Arc<$inner>) -> alloc::sync::Arc<Self> {
+ let ptr = alloc::sync::Arc::into_raw($from_arc_arg_name);
+ // SAFETY: the pointer is created by casting a pointer that is pointing to an object
+ // with the same layout and validity invariants and the previous pointer was created
+ // directly from box. (Notice repr(transparent).)
+ unsafe { alloc::sync::Arc::from_raw(ptr as *mut Self) }
+ }
+ }
+ }
+}
+pub(crate) use _transparent_ref_conversion;
+
+macro_rules! _check_tts_eq {
+ ($left:tt, $right:tt, $message:literal) => {
+ macro_rules! token_eq {
+ ($right) => {};
+ ($any:tt) => {
+ compile_error!($message)
+ };
+ }
+ token_eq!($left);
+ };
+}
+pub(crate) use _check_tts_eq;
+
+macro_rules! _transparent_newtype_inner_type {
+ ($(#[$($field_attr:tt)*])* $inner:ty) => {
+ $inner
+ };
+ ($(#[$($phantom_attr:tt)*])* PhantomData<$phantom:ty>, $(#[$($field_attr:tt)*])* $inner:ty) => {
+ $inner
+ };
+}
+pub(crate) use _transparent_newtype_inner_type;
diff --git a/internals/src/macros.rs b/internals/src/macros.rs
index 1119fcde..f42786fe 100644
--- a/internals/src/macros.rs
+++ b/internals/src/macros.rs
@@ -54,6 +54,7 @@ macro_rules! impl_to_hex_from_lower_hex {
///
/// The `alloc` types MUST NOT have import paths and don't need imports.
#[macro_export]
+#[deprecated(since = "TBD", note = "use include!(\"../../include/newtype.rs\") instead")]
macro_rules! transparent_newtype {
(
$(#[$($struct_attr:tt)*])*
diff --git a/io/src/bridge.rs b/io/src/bridge.rs
index 371dde8b..84aafc23 100644
--- a/io/src/bridge.rs
+++ b/io/src/bridge.rs
@@ -2,7 +2,8 @@
use internals::rust_version;
-internals::transparent_newtype! {
+// Defined in `REPO_DIR/include/newtype.rs`.
+crate::transparent_newtype! {
/// A bridging wrapper providing the I/O traits for types that already implement `std` I/O traits.
#[derive(Debug)]
pub struct FromStd<T>(T);
@@ -108,7 +109,8 @@ impl<T: std::io::Write> std::io::Write for FromStd<T> {
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { self.0.write_all(buf) }
}
-internals::transparent_newtype! {
+// Defined in `REPO_DIR/include/newtype.rs`.
+crate::transparent_newtype! {
/// A bridging wrapper providing the std traits for types that already implement our traits.
#[derive(Debug)]
pub struct ToStd<T>(T);
diff --git a/io/src/lib.rs b/io/src/lib.rs
index c38720a7..a7079d97 100644
--- a/io/src/lib.rs
+++ b/io/src/lib.rs
@@ -648,6 +648,9 @@ impl<D> From<Error> for ReadError<D> {
fn from(e: Error) -> Self { Self::Io(e) }
}
+#[cfg(feature = "std")]
+include!("../../include/newtype.rs"); // Explained in `REPO_DIR/docs/README.md`.
+
#[cfg(test)]
mod tests {
#[cfg(all(not(feature = "std"), feature = "alloc"))]
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index 3e14fc71..fc209da4 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -124,3 +124,6 @@ pub(crate) fn compact_size_encode(value: usize) -> ArrayVec<u8, 9> {
let encoder = encoding::CompactSizeEncoder::new(value);
ArrayVec::from_slice(encoder.current_chunk())
}
+
+#[cfg(feature = "alloc")]
+include!("../../include/newtype.rs"); // Explained in `REPO_DIR/docs/README.md`.
diff --git a/primitives/src/script/borrowed.rs b/primitives/src/script/borrowed.rs
index 07feb004..4e44861b 100644
--- a/primitives/src/script/borrowed.rs
+++ b/primitives/src/script/borrowed.rs
@@ -14,7 +14,8 @@ use encoding::{BytesEncoder, CompactSizeEncoder, Encodable, Encoder2};
use super::ScriptBuf;
use crate::prelude::{Box, ToOwned, Vec};
-internals::transparent_newtype! {
+// Defined in `REPO_DIR/include/newtype.rs`.
+crate::transparent_newtype! {
/// Bitcoin script slice.
///
/// *[See also the `bitcoin::script` module](super).*
@@ -61,6 +62,8 @@ internals::transparent_newtype! {
/// of different APIs and trait implementations. Please see [`examples/script.rs`] for a
/// thorough example of all the APIs.
///
+ /// [`examples/script.rs`]: <https://github.com/rust-bitcoin/rust-bitcoin/blob/master/bitcoin/examples/script.rs>
+ ///
/// # Bitcoin Core References
///
/// * [CScript definition](https://github.com/bitcoin/bitcoin/blob/d492dc1cdaabdc52b0766bf4cba4bd73178325d0/src/script/script.h#L410)
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.