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.as_any()
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    fn as_any(&self) -> &dyn std::any::Any;
78}
79
80impl<T> CloneTransport for T
81where
82    T: Transport + Clone + Send + Sync,
83{
84    #[inline]
85    fn clone_box(&self) -> Box<dyn CloneTransport + Send + Sync> {
86        Box::new(self.clone())
87    }
88
89    #[inline]
90    fn as_any(&self) -> &dyn std::any::Any {
91        self
92    }
93}
94
95impl Service<RequestPacket> for BoxTransport {
96    type Response = ResponsePacket;
97    type Error = TransportError;
98    type Future = TransportFut<'static>;
99
100    #[inline]
101    fn poll_ready(
102        &mut self,
103        cx: &mut std::task::Context<'_>,
104    ) -> std::task::Poll<Result<(), Self::Error>> {
105        self.inner.poll_ready(cx)
106    }
107
108    #[inline]
109    fn call(&mut self, req: RequestPacket) -> Self::Future {
110        self.inner.call(req)
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use super::*;
117
118    #[derive(Clone)]
119    struct DummyTransport<T>(T);
120    impl<T> Service<RequestPacket> for DummyTransport<T> {
121        type Response = ResponsePacket;
122        type Error = TransportError;
123        type Future = TransportFut<'static>;
124
125        fn poll_ready(
126            &mut self,
127            _cx: &mut std::task::Context<'_>,
128        ) -> std::task::Poll<Result<(), Self::Error>> {
129            unimplemented!()
130        }
131
132        fn call(&mut self, _req: RequestPacket) -> Self::Future {
133            unimplemented!()
134        }
135    }
136
137    // checks trait + send + sync + 'static
138    const fn _compile_check() {
139        const fn inner<T>()
140        where
141            T: Transport + CloneTransport + Send + Sync + Clone + IntoBoxTransport + 'static,
142        {
143        }
144        inner::<BoxTransport>();
145    }
146
147    #[test]
148    fn no_reboxing() {
149        let id = TypeId::of::<DummyTransport<()>>();
150        no_reboxing_(DummyTransport(()), id);
151        no_reboxing_(BoxTransport::new(DummyTransport(())), id);
152
153        let wrap = String::from("hello");
154        let id = TypeId::of::<DummyTransport<String>>();
155        no_reboxing_(DummyTransport(wrap.clone()), id);
156        no_reboxing_(BoxTransport::new(DummyTransport(wrap)), id);
157    }
158
159    fn no_reboxing_<T: IntoBoxTransport>(t: T, id: TypeId) {
160        eprintln!("{}", std::any::type_name::<T>());
161
162        let t1 = BoxTransport::new(t);
163        let t1p = std::ptr::addr_of!(*t1.inner);
164        let t1id = t1.as_any().type_id();
165
166        // This shouldn't wrap `t1` in another box (`BoxTransport<BoxTransport<_>>`).
167        let t2 = BoxTransport::new(t1);
168        let t2p = std::ptr::addr_of!(*t2.inner);
169        let t2id = t2.as_any().type_id();
170
171        assert_eq!(t1id, id);
172        assert_eq!(t1id, t2id);
173        assert!(std::ptr::eq(t1p, t2p));
174    }
175}