std_util/
nested_ref.rs

1//! Tools for chaining borrows of [`std::cell::RefCell`].
2//!
3//! Useful for returning an abstract borrow, where multiple inner borrows are required. e.g.:
4//!
5//! ```
6//! use std::cell::RefCell;
7//!
8//! struct MyPrivateType {
9//!   x: RefCell<i32>
10//! }
11//!
12//! pub struct MyPublicType {
13//!   inner: RefCell<MyPrivateType>
14//! }
15//!
16//! impl MyPublicType {
17//!   pub fn borrow_x(&self) -> impl std::ops::Deref<Target=i32> + '_ {
18//!     std_util::nested_ref::NestedRef::map(self.inner.borrow(), |inner| inner.x.borrow())
19//!   }
20//! }
21//! ```
22//!
23//! Currently only supports nested RefCells; i.e. one level of nesting.
24//!
25//! TODO: It might be feasible to genericize the reference types, which would
26//! also add support for arbitrary levels of nesting.
27use std::cell::{Ref, RefMut};
28
29/// A nested [`std::cell::Ref`]. Useful for chaining borrows with [`std::cell::RefCell`].
30pub struct NestedRef<'a, Inner, Outer>
31where
32    Inner: 'static,
33{
34    // unsafely points to the Ref inside `_outer`.
35    // Must be declared first so that it's dropped first.
36    inner: Ref<'a, Inner>,
37    // Boxed so that `Self` is movable without breaking `inner`'s reference.
38    _outer: Box<Ref<'a, Outer>>,
39}
40impl<'a, Inner, Outer> NestedRef<'a, Inner, Outer>
41where
42    Inner: 'static,
43{
44    #[inline]
45    pub fn map(outer: Ref<'a, Outer>, borrow_fn: impl FnOnce(&Outer) -> Ref<Inner>) -> Self {
46        Self::filter_map(outer, |outer| Some(borrow_fn(outer))).unwrap()
47    }
48
49    #[inline]
50    pub fn filter_map(
51        outer: Ref<'a, Outer>,
52        borrow_fn: impl FnOnce(&Outer) -> Option<Ref<Inner>>,
53    ) -> Option<Self> {
54        let boxed_outer = Box::new(outer);
55        let inner: Ref<Inner> = borrow_fn(&boxed_outer)?;
56        // SAFETY: The lifetime of the `inner` returned from `borrow_fn` is the
57        // (anonymous) lifetime of `boxed_outer`. However we only provided the
58        // closure with the reference to the *contents* of the box, `outer`, which
59        // has lifetime 'a. This is safe as long as we ensure that the transmuted
60        // `boxed_outer` outlives the transmuted `inner`.
61        let inner: Ref<'a, Inner> = unsafe { std::mem::transmute(inner) };
62        Some(Self {
63            inner,
64            _outer: boxed_outer,
65        })
66    }
67}
68impl<Inner, Outer> std::ops::Deref for NestedRef<'_, Inner, Outer>
69where
70    Inner: 'static,
71{
72    type Target = Inner;
73
74    fn deref(&self) -> &Self::Target {
75        self.inner.deref()
76    }
77}
78
79/// A nested [`std::cell::Ref`]. Useful for chaining a mutable borrow of a
80/// [`std::cell::RefCell`].
81pub struct NestedRefMut<'a, Inner, Outer>
82where
83    Inner: 'static,
84{
85    // unsafely points to the Ref inside `_outer`.
86    // Must be declared first so that it's dropped first.
87    inner: RefMut<'a, Inner>,
88    // Boxed so that `Self` is movable without breaking `inner`'s reference.
89    _outer: Box<Ref<'a, Outer>>,
90}
91impl<'a, Inner, Outer> NestedRefMut<'a, Inner, Outer>
92where
93    Inner: 'static,
94{
95    #[inline]
96    pub fn map(outer: Ref<'a, Outer>, borrow_fn: impl FnOnce(&Outer) -> RefMut<Inner>) -> Self {
97        Self::filter_map(outer, |outer| Some(borrow_fn(outer))).unwrap()
98    }
99
100    #[inline]
101    pub fn filter_map(
102        outer: Ref<'a, Outer>,
103        borrow_fn: impl FnOnce(&Outer) -> Option<RefMut<Inner>>,
104    ) -> Option<Self> {
105        let outer = Box::new(outer);
106        let inner: RefMut<Inner> = borrow_fn(&outer)?;
107        // SAFETY: The lifetime of the `inner` returned from `borrow_fn` is the
108        // (anonymous) lifetime of `boxed_outer`. However we only provided the
109        // closure with the reference to the *contents* of the box, `outer`, which
110        // has lifetime 'a. This is safe as long as we ensure that the transmuted
111        // `boxed_outer` outlives the transmuted `inner`.
112        let inner: RefMut<'a, Inner> = unsafe { std::mem::transmute(inner) };
113        Some(Self {
114            inner,
115            _outer: outer,
116        })
117    }
118}
119impl<Inner, Outer> std::ops::Deref for NestedRefMut<'_, Inner, Outer>
120where
121    Inner: 'static,
122{
123    type Target = Inner;
124
125    fn deref(&self) -> &Self::Target {
126        self.inner.deref()
127    }
128}
129impl<Inner, Outer> std::ops::DerefMut for NestedRefMut<'_, Inner, Outer>
130where
131    Inner: 'static,
132{
133    fn deref_mut(&mut self) -> &mut Self::Target {
134        self.inner.deref_mut()
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use std::cell::RefCell;
141
142    use super::*;
143
144    struct TestOuter {
145        x: RefCell<i32>,
146    }
147
148    #[test]
149    fn nestedref_map() {
150        let outer = RefCell::new(TestOuter {
151            x: RefCell::new(42),
152        });
153        let nested = NestedRef::map(outer.borrow(), |inner| inner.x.borrow());
154        assert_eq!(*nested, 42);
155    }
156
157    #[test]
158    fn nestedref_filter_map_some() {
159        let outer = RefCell::new(TestOuter {
160            x: RefCell::new(42),
161        });
162        let nested = NestedRef::filter_map(outer.borrow(), |inner| Some(inner.x.borrow()));
163        let nested = nested.unwrap();
164        assert_eq!(*nested, 42);
165    }
166
167    #[test]
168    fn nestedref_filter_map_none() {
169        let outer = RefCell::new(TestOuter {
170            x: RefCell::new(42),
171        });
172        let nested = NestedRef::<i32, TestOuter>::filter_map(outer.borrow(), |_inner| None);
173        assert!(nested.is_none());
174    }
175
176    #[test]
177    fn nestedref_is_movable() {
178        let outer = RefCell::new(TestOuter {
179            x: RefCell::new(42),
180        });
181        let nested = NestedRef::map(outer.borrow(), |inner| inner.x.borrow());
182        assert_eq!(*nested, 42);
183        let boxed_nested = Box::new(nested);
184        assert_eq!(**boxed_nested, 42);
185    }
186
187    #[test]
188    fn nestedrefmut_map() {
189        let outer = RefCell::new(TestOuter {
190            x: RefCell::new(42),
191        });
192        {
193            let mut nested = NestedRefMut::map(outer.borrow(), |inner| inner.x.borrow_mut());
194            assert_eq!(*nested, 42);
195            *nested += 1;
196            assert_eq!(*nested, 43);
197            assert!(outer.borrow().x.try_borrow().is_err());
198        }
199        assert_eq!(*outer.borrow().x.borrow(), 43);
200    }
201
202    #[test]
203    fn nestedrefmut_filter_map_some() {
204        let outer = RefCell::new(TestOuter {
205            x: RefCell::new(42),
206        });
207        {
208            let nested =
209                NestedRefMut::filter_map(outer.borrow(), |inner| Some(inner.x.borrow_mut()));
210            let mut nested = nested.unwrap();
211            assert_eq!(*nested, 42);
212            *nested += 1;
213            assert_eq!(*nested, 43);
214            assert!(outer.borrow().x.try_borrow().is_err());
215        }
216        assert_eq!(*outer.borrow().x.borrow(), 43);
217    }
218
219    #[test]
220    fn nestedrefmut_filter_map_none() {
221        let outer = RefCell::new(TestOuter {
222            x: RefCell::new(42),
223        });
224        let nested = NestedRefMut::<i32, TestOuter>::filter_map(outer.borrow(), |_inner| None);
225        assert!(nested.is_none());
226    }
227
228    #[test]
229    fn nestedrefmut_is_movable() {
230        let outer = RefCell::new(TestOuter {
231            x: RefCell::new(42),
232        });
233        {
234            let nested = NestedRefMut::map(outer.borrow(), |inner| inner.x.borrow_mut());
235            assert_eq!(*nested, 42);
236            let mut nested = Box::new(nested);
237            assert_eq!(**nested, 42);
238            **nested += 1;
239            assert_eq!(**nested, 43);
240        }
241        assert_eq!(*outer.borrow().x.borrow(), 43);
242    }
243}