mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-26 11:01:16 -03:00
Introduce ScopedCell and ScopedRefCell
As part of a plan to overhaul the "scoped_set" pattern, introduce two new "scoped" types: ScopedCell is a trivial wrapper around Cell which exposes a function `scoped_mod`. This allows you to modify the contents of the Cell; the original contents are restored when the guard is dropped. ScopedRefCell is similar, except it requires the caller to supply an accessor which returns a &mut field, and also the new value. These will be used to replace the rather unsightly scoped_push calls around the fish codebase.
This commit is contained in:
142
src/common.rs
142
src/common.rs
@@ -21,6 +21,7 @@
|
||||
use core::slice;
|
||||
use libc::{EIO, O_WRONLY, SIGTTOU, SIG_IGN, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::ffi::{CStr, CString, OsStr, OsString};
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -1676,6 +1677,147 @@ pub fn get_executable_path(argv0: impl AsRef<Path>) -> PathBuf {
|
||||
argv0.as_ref().to_owned()
|
||||
}
|
||||
|
||||
/// A wrapper around Cell which supports modifying the contents, scoped to a region of code.
|
||||
/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the value,
|
||||
/// instead of requiring an accessor function which returns a mutable reference to a field.
|
||||
pub struct ScopedCell<T>(Cell<T>);
|
||||
|
||||
impl<T> Deref for ScopedCell<T> {
|
||||
type Target = Cell<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ScopedCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> ScopedCell<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(Cell::new(value))
|
||||
}
|
||||
|
||||
/// Temporarily modify a value in the ScopedCell, restoring it when the returned object is dropped.
|
||||
///
|
||||
/// This is useful when you want to apply a change for the duration of a scope
|
||||
/// without having to manually restore the previous value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use fish::common::ScopedCell;
|
||||
///
|
||||
/// let cell = ScopedCell::new(5);
|
||||
/// assert_eq!(cell.get(), 5);
|
||||
///
|
||||
/// {
|
||||
/// let _guard = cell.scoped_mod(|v| *v += 10);
|
||||
/// assert_eq!(cell.get(), 15);
|
||||
/// }
|
||||
///
|
||||
/// // Restored after scope
|
||||
/// assert_eq!(cell.get(), 5);
|
||||
/// ```
|
||||
pub fn scoped_mod<'a, Modifier: FnOnce(&mut T)>(
|
||||
&'a self,
|
||||
modifier: Modifier,
|
||||
) -> impl ScopeGuarding + 'a {
|
||||
let mut val = self.get();
|
||||
modifier(&mut val);
|
||||
let saved = self.replace(val);
|
||||
ScopeGuard::new(self, move |cell| (*cell).set(saved))
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around RefCell which supports modifying the contents, scoped to a region of code.
|
||||
pub struct ScopedRefCell<T>(RefCell<T>);
|
||||
|
||||
impl<T> Deref for ScopedRefCell<T> {
|
||||
type Target = RefCell<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ScopedRefCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ScopedRefCell<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(RefCell::new(value))
|
||||
}
|
||||
|
||||
/// Temporarily modify a field in the ScopedRefCell, restoring it when the returned guard is dropped.
|
||||
///
|
||||
/// This is useful when you want to change part of a data structure for the duration of a scope,
|
||||
/// and automatically restore the original value afterward.
|
||||
///
|
||||
/// The `accessor` function selects the field to modify by returning a mutable reference to it.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use fish::common::ScopedRefCell;
|
||||
///
|
||||
/// struct State { flag: bool }
|
||||
///
|
||||
/// let cell = ScopedRefCell::new(State { flag: false });
|
||||
/// assert_eq!(cell.borrow().flag, false);
|
||||
///
|
||||
/// {
|
||||
/// let _guard = cell.scoped_set(true, |s| &mut s.flag);
|
||||
/// assert_eq!(cell.borrow().flag, true);
|
||||
/// }
|
||||
///
|
||||
/// // Restored after scope
|
||||
/// assert_eq!(cell.borrow().flag, false);
|
||||
/// ```
|
||||
pub fn scoped_set<'a, Accessor, Value: 'a>(
|
||||
&'a self,
|
||||
value: Value,
|
||||
accessor: Accessor,
|
||||
) -> impl ScopeGuarding + 'a
|
||||
where
|
||||
Accessor: Fn(&mut T) -> &mut Value + 'a,
|
||||
{
|
||||
let mut data = self.borrow_mut();
|
||||
let mut saved = std::mem::replace(accessor(&mut data), value);
|
||||
ScopeGuard::new(self, move |cell| {
|
||||
let mut data = cell.borrow_mut();
|
||||
std::mem::swap((accessor)(&mut data), &mut saved);
|
||||
})
|
||||
}
|
||||
|
||||
/// Convenience method for replacing the entire contents of the ScopedRefCell, restoring it when dropped.
|
||||
///
|
||||
/// Equivalent to `scoped_set(value, |s| s)`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use fish::common::ScopedRefCell;
|
||||
///
|
||||
/// let cell = ScopedRefCell::new(10);
|
||||
/// assert_eq!(*cell.borrow(), 10);
|
||||
///
|
||||
/// {
|
||||
/// let _guard = cell.scoped_replace(99);
|
||||
/// assert_eq!(*cell.borrow(), 99);
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(*cell.borrow(), 10);
|
||||
/// ```
|
||||
pub fn scoped_replace<'a>(&'a self, value: T) -> impl ScopeGuarding + 'a {
|
||||
self.scoped_set(value, |s| s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A RAII cleanup object. Unlike in C++ where there is no borrow checker, we can't just provide a
|
||||
/// callback that modifies live objects willy-nilly because then there would be two &mut references
|
||||
/// to the same object - the original variables we keep around to use and their captured references
|
||||
|
||||
@@ -1,6 +1,41 @@
|
||||
use crate::common::{scoped_push, truncate_at_nul, ScopeGuard};
|
||||
use crate::common::{scoped_push, truncate_at_nul, ScopeGuard, ScopedCell, ScopedRefCell};
|
||||
use crate::wchar::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_scoped_cell() {
|
||||
let cell = ScopedCell::new(42);
|
||||
|
||||
{
|
||||
let _guard = cell.scoped_mod(|x| *x += 1);
|
||||
assert_eq!(cell.get(), 43);
|
||||
}
|
||||
|
||||
assert_eq!(cell.get(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scoped_refcell() {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct Data {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
let cell = ScopedRefCell::new(Data { x: 1, y: 2 });
|
||||
|
||||
{
|
||||
let _guard = cell.scoped_set(10, |d| &mut d.x);
|
||||
assert_eq!(cell.borrow().x, 10);
|
||||
}
|
||||
assert_eq!(cell.borrow().x, 1);
|
||||
|
||||
{
|
||||
let _guard = cell.scoped_replace(Data { x: 42, y: 99 });
|
||||
assert_eq!(*cell.borrow(), Data { x: 42, y: 99 });
|
||||
}
|
||||
assert_eq!(*cell.borrow(), Data { x: 1, y: 2 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scoped_push() {
|
||||
struct Context {
|
||||
|
||||
Reference in New Issue
Block a user