alloy_transport/
boxed.rs

1use crate::{Transport, TransportError, TransportFut};
2use alloy_json_rpc::{RequestPacket, ResponsePacket};
3use std::{any::TypeId, fmt};
4use tower::Service;
5
6#[expect(unnameable_types)]
7mod private {
8    pub trait Sealed {}
9    impl<T: super::Transport + Clone> Sealed for T {}
10}
11
12/// Trait for converting a transport into a boxed transport.
13///
14/// This trait is sealed and implemented for all types that implement
15/// [`Transport`] + [`Clone`].
16pub trait IntoBoxTransport: Transport + Clone + private::Sealed {
17    /// Boxes the transport.
18    fn into_box_transport(self) -> BoxTransport;
19}
20
21impl<T: Transport + Clone> IntoBoxTransport for T {
22    fn into_box_transport(self) -> BoxTransport {
23        // "specialization" to re-use `BoxTransport`.
24        if TypeId::of::<T>() == TypeId::of::<BoxTransport>() {
25            // This is not `transmute` because it doesn't allow size mismatch at compile time.
26            // `transmute_copy` is a work-around for `transmute_unchecked` not being stable.
27            // SAFETY: `self` is `BoxTransport`. This is a no-op.
28            let this = std::mem::ManuallyDrop::new(self);
29            return unsafe { std::mem::transmute_copy(&this) };
30        }
31        BoxTransport { inner: Box::new(self) }
32    }
33}
34
35/// A boxed, Clone-able [`Transport`] trait object.
36///
37/// This type allows RPC clients to use a type-erased transport. It is
38/// [`Clone`] and [`Send`] + [`Sync`], and implements [`Transport`]. This
39/// allows for complex behavior abstracting across several different clients
40/// with different transport types.
41///
42/// All higher-level types, such as `RpcClient`, use this type internally
43/// rather than a generic [`Transport`] parameter.
44pub struct BoxTransport {
45    inner: Box<dyn CloneTransport>,
46}
47
48impl BoxTransport {
49    /// Instantiate a new box transport from a suitable transport.
50    #[inline]
51    pub fn new<T: IntoBoxTransport>(transport: T) -> Self {
52        transport.into_box_transport()
53    }
54
55    /// Returns a reference to the inner transport.
56    #[inline]
57    pub fn as_any(&self) -> &dyn std::any::Any {
58        &*self.inner
59    }
60}
61
62impl fmt::Debug for BoxTransport {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_struct("BoxTransport").finish_non_exhaustive()
65    }
66}
67
68impl Clone for BoxTransport {
69    fn clone(&self) -> Self {
70        Self { inner: self.inner.clone_box() }
71    }
72}
73
74/// Helper trait for constructing [`BoxTransport`].
75trait CloneTransport: Transport + std::any::Any {
76    fn clone_box(&self) -> Box<dyn CloneTransport + Send + Sync>;
77}
78
79impl<T> CloneTransport for T
80where
81    T: Transport + Clone + Send + Sync,
82{
83    #[inline]
84    fn clone_box(&self) -> Box<dyn CloneTransport + Send + Sync> {
85        Box::new(self.clone())
86    }
87}
88
89impl Service<RequestPacket> for BoxTransport {
90    type Response = ResponsePacket;
91    type Error = TransportError;
92    type Future = TransportFut<'static>;
93
94    #[inline]
95    fn poll_ready(
96        &mut self,
97        cx: &mut std::task::Context<'_>,
98    ) -> std::task::Poll<Result<(), Self::Error>> {
99        self.inner.poll_ready(cx)
100    }
101
102    #[inline]
103    fn call(&mut self, req: RequestPacket) -> Self::Future {
104        self.inner.call(req)
105    }
106}
107
108#[cfg(test)]
109mod test {
110    use super::*;
111
112    #[derive(Clone)]
113    struct DummyTransport<T>(T);
114    impl<T> Service<RequestPacket> for DummyTransport<T> {
115        type Response = ResponsePacket;
116        type Error = TransportError;
117        type Future = TransportFut<'static>;
118
119        fn poll_ready(
120            &mut self,
121            _cx: &mut std::task::Context<'_>,
122        ) -> std::task::Poll<Result<(), Self::Error>> {
123            unimplemented!()
124        }
125
126        fn call(&mut self, _req: RequestPacket) -> Self::Future {
127            unimplemented!()
128        }
129    }
130
131    // checks trait + send + sync + 'static
132    const fn _compile_check() {
133        const fn inner<T>()
134        where
135            T: Transport + CloneTransport + Send + Sync + Clone + IntoBoxTransport + 'static,
136        {
137        }
138        inner::<BoxTransport>();
139    }
140
141    #[test]
142    fn no_reboxing() {
143        let id = TypeId::of::<DummyTransport<()>>();
144        no_reboxing_(DummyTransport(()), id);
145        no_reboxing_(BoxTransport::new(DummyTransport(())), id);
146
147        let wrap = String::from("hello");
148        let id = TypeId::of::<DummyTransport<String>>();
149        no_reboxing_(DummyTransport(wrap.clone()), id);
150        no_reboxing_(BoxTransport::new(DummyTransport(wrap)), id);
151    }
152
153    fn no_reboxing_<T: IntoBoxTransport>(t: T, id: TypeId) {
154        eprintln!("{}", std::any::type_name::<T>());
155
156        let t1 = BoxTransport::new(t);
157        let t1p = std::ptr::addr_of!(*t1.inner);
158        let t1id = t1.as_any().type_id();
159
160        // This shouldn't wrap `t1` in another box (`BoxTransport<BoxTransport<_>>`).
161        let t2 = BoxTransport::new(t1);
162        let t2p = std::ptr::addr_of!(*t2.inner);
163        let t2id = t2.as_any().type_id();
164
165        assert_eq!(t1id, id);
166        assert_eq!(t1id, t2id);
167        assert!(std::ptr::eq(t1p, t2p));
168    }
169}