From b7958d3d17608a89f3888f6f5f6f6b5b28ab86d2 Mon Sep 17 00:00:00 2001 From: John Costa Date: Mon, 9 Mar 2026 14:10:15 -0700 Subject: [PATCH] feat(signals): add WritableResultExt trait (#5338) * feat(signals): add WritableResultExt trait Add WritableResultExt mirroring the existing ReadableResultExt, providing mutable access to signal values holding Result. Methods: - as_mut(): returns Result, WritableRef> - unwrap_mut(): returns WritableRef, panics if Err Closes #5293 * refactor(signals): use descriptive expect messages in WritableResultExt::as_mut Replace bare .unwrap() calls with .expect() containing a descriptive message about the failure mode. This gives better diagnostics if the Result variant changes between the read and write lock acquisitions. * switch to a single write to avoid race conditions with sync signals --------- Co-authored-by: Evan Almloff --- packages/signals/src/read.rs | 16 ++++++++++++--- packages/signals/src/write.rs | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/signals/src/read.rs b/packages/signals/src/read.rs index 42442f675..98e8b1bce 100644 --- a/packages/signals/src/read.rs +++ b/packages/signals/src/read.rs @@ -419,9 +419,19 @@ pub trait ReadableResultExt: Readable> { T: 'static, E: 'static, { - ::try_map(self.read(), |v| v.as_ref().ok()).ok_or( - ::map(self.read(), |v| v.as_ref().err().unwrap()), - ) + let read = self.read(); + match read.deref() { + Ok(_) => Ok(::map(read, |v| { + v.as_ref() + .ok() + .expect("Result variant changed between read and map") + })), + Err(_) => Err(::map(read, |v| { + v.as_ref() + .err() + .expect("Result variant changed between read and map") + })), + } } } diff --git a/packages/signals/src/write.rs b/packages/signals/src/write.rs index 13b4107fa..1d3c68966 100644 --- a/packages/signals/src/write.rs +++ b/packages/signals/src/write.rs @@ -447,6 +447,44 @@ pub trait WritableOptionExt: Writable> { impl WritableOptionExt for W where W: Writable> {} +/// An extension trait for [`Writable>`] that provides some convenience methods. +pub trait WritableResultExt: Writable> { + /// Unwraps the inner value mutably, panicking if the Result is an error. + #[track_caller] + fn unwrap_mut(&mut self) -> WritableRef<'_, Self, T> + where + T: 'static, + E: 'static, + { + WriteLock::filter_map(self.write(), |v| v.as_mut().ok()) + .expect("Tried to unwrap a Result that was an error") + } + + /// Attempts to mutably access the inner value of the Result. + #[track_caller] + fn as_mut(&mut self) -> Result, WritableRef<'_, Self, E>> + where + T: 'static, + E: 'static, + { + let write = self.write(); + match write.as_ref() { + Ok(_) => Ok(WriteLock::map(write, |v| { + v.as_mut() + .ok() + .expect("Result variant changed between read and write") + })), + Err(_) => Err(WriteLock::map(write, |v| { + v.as_mut() + .err() + .expect("Result variant changed between read and write") + })), + } + } +} + +impl WritableResultExt for W where W: Writable> {} + /// An extension trait for [`Writable>`] that provides some convenience methods. pub trait WritableVecExt: Writable> { /// Pushes a new value to the end of the vector.