diff options
| author | Mara Bos <m-ou.se@m-ou.se> | 2021-01-16 17:29:45 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-16 17:29:45 +0000 |
| commit | 6bb06f4f2387e2369d98cf87d79b772f99ae979f (patch) | |
| tree | e62c46ec9e6565c5a6bd19593627ee1dea97aa04 | |
| parent | 63a83c5f55801b17b77adf690db397d17c706c48 (diff) | |
| parent | c625b979aed9ad3c8380ea2239d9345d4cec695a (diff) | |
| download | rust-6bb06f4f2387e2369d98cf87d79b772f99ae979f.tar.gz rust-6bb06f4f2387e2369d98cf87d79b772f99ae979f.zip | |
Rollup merge of #78455 - udoprog:refcell-opt-map, r=KodrAus
Introduce {Ref, RefMut}::try_map for optional projections in RefCell
This fills a usability gap of `RefCell` I've personally encountered to perform optional projections, mostly into collections such as `RefCell<Vec<T>>` or `RefCell<HashMap<U, T>>`:
> This kind of API was briefly featured under Open questions in #10514 back in 2013 (!)
```rust
let values = RefCell::new(vec![1, 2, 3, 4]);
let b = Ref::opt_map(values.borrow(), |vec| vec.get(2));
```
It primarily avoids this alternative approach to accomplish the same kind of projection which is both rather noisy and panicky:
```rust
let values = RefCell::new(vec![1, 2, 3, 4]);
let b = if values.get(2).is_some() {
Some(Ref::map(values.borrow(), |vec| vec.get(2).unwrap()))
} else {
None
};
```
### Open questions
The naming `opt_map` is preliminary. I'm not aware of prior art in std to lean on here, but this name should probably be improved if this functionality is desirable.
Since `opt_map` consumes the guard, and alternative syntax might be more appropriate which instead *tries* to perform the projection, allowing the original borrow to be recovered in case it fails:
```rust
pub fn try_map<U: ?Sized, F>(orig: Ref<'b, T>, f: F) -> Result<Ref<'b, U>, Self>
where
F: FnOnce(&T) -> Option<&U>;
```
This would be more in line with the `try_map` method [provided by parking lot](https://docs.rs/lock_api/0/lock_api/struct.RwLockWriteGuard.html#method.try_map).
| -rw-r--r-- | library/core/src/cell.rs | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index c5ab7a39ff0..fa0fbaa35c9 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -1261,6 +1261,40 @@ impl<'b, T: ?Sized> Ref<'b, T> { Ref { value: f(orig.value), borrow: orig.borrow } } + /// Makes a new `Ref` for an optional component of the borrowed data. The + /// original guard is returned as an `Err(..)` if the closure returns + /// `None`. + /// + /// The `RefCell` is already immutably borrowed, so this cannot fail. + /// + /// This is an associated function that needs to be used as + /// `Ref::filter_map(...)`. A method would interfere with methods of the same + /// name on the contents of a `RefCell` used through `Deref`. + /// + /// # Examples + /// + /// ``` + /// #![feature(cell_filter_map)] + /// + /// use std::cell::{RefCell, Ref}; + /// + /// let c = RefCell::new(vec![1, 2, 3]); + /// let b1: Ref<Vec<u32>> = c.borrow(); + /// let b2: Result<Ref<u32>, _> = Ref::filter_map(b1, |v| v.get(1)); + /// assert_eq!(*b2.unwrap(), 2); + /// ``` + #[unstable(feature = "cell_filter_map", reason = "recently added", issue = "81061")] + #[inline] + pub fn filter_map<U: ?Sized, F>(orig: Ref<'b, T>, f: F) -> Result<Ref<'b, U>, Self> + where + F: FnOnce(&T) -> Option<&U>, + { + match f(orig.value) { + Some(value) => Ok(Ref { value, borrow: orig.borrow }), + None => Err(orig), + } + } + /// Splits a `Ref` into multiple `Ref`s for different components of the /// borrowed data. /// @@ -1372,6 +1406,58 @@ impl<'b, T: ?Sized> RefMut<'b, T> { RefMut { value: f(value), borrow } } + /// Makes a new `RefMut` for an optional component of the borrowed data. The + /// original guard is returned as an `Err(..)` if the closure returns + /// `None`. + /// + /// The `RefCell` is already mutably borrowed, so this cannot fail. + /// + /// This is an associated function that needs to be used as + /// `RefMut::filter_map(...)`. A method would interfere with methods of the + /// same name on the contents of a `RefCell` used through `Deref`. + /// + /// # Examples + /// + /// ``` + /// #![feature(cell_filter_map)] + /// + /// use std::cell::{RefCell, RefMut}; + /// + /// let c = RefCell::new(vec![1, 2, 3]); + /// + /// { + /// let b1: RefMut<Vec<u32>> = c.borrow_mut(); + /// let mut b2: Result<RefMut<u32>, _> = RefMut::filter_map(b1, |v| v.get_mut(1)); + /// + /// if let Ok(mut b2) = b2 { + /// *b2 += 2; + /// } + /// } + /// + /// assert_eq!(*c.borrow(), vec![1, 4, 3]); + /// ``` + #[unstable(feature = "cell_filter_map", reason = "recently added", issue = "81061")] + #[inline] + pub fn filter_map<U: ?Sized, F>(orig: RefMut<'b, T>, f: F) -> Result<RefMut<'b, U>, Self> + where + F: FnOnce(&mut T) -> Option<&mut U>, + { + // FIXME(nll-rfc#40): fix borrow-check + let RefMut { value, borrow } = orig; + let value = value as *mut T; + // SAFETY: function holds onto an exclusive reference for the duration + // of its call through `orig`, and the pointer is only de-referenced + // inside of the function call never allowing the exclusive reference to + // escape. + match f(unsafe { &mut *value }) { + Some(value) => Ok(RefMut { value, borrow }), + None => { + // SAFETY: same as above. + Err(RefMut { value: unsafe { &mut *value }, borrow }) + } + } + } + /// Splits a `RefMut` into multiple `RefMut`s for different components of the /// borrowed data. /// |
