thruput_sdk/storage/value.rs
1// src/storage/value.rs
2use super::keypath::*;
3use core::marker::PhantomData;
4pub use core::ops::{Deref, DerefMut};
5
6/// A typed storage slot for serialized values.
7///
8/// `Value<T>` is a handle to a storage location that holds a serialized `T`.
9/// All operations are immediate syscalls — no buffering.
10/// For in-place modification with auto-flush, use `load_mut()`.
11#[derive(Debug)]
12pub struct Value<T> {
13 key: LeafKey,
14 _marker: PhantomData<fn() -> T>,
15}
16
17impl<T> From<Keypath> for Value<T> {
18 fn from(keypath: Keypath) -> Self {
19 Self {
20 key: LeafKey::from(keypath),
21 _marker: PhantomData,
22 }
23 }
24}
25
26impl<T> super::sealed::Sealed for Value<T> {}
27impl<T> super::node::StorageType for Value<T> {}
28
29impl<T> Value<T> {
30 /// Return this value's storage key.
31 pub fn key(&self) -> &crate::types::StorageKey {
32 self.key.get()
33 }
34}
35
36impl<T> Value<T>
37where
38 T: for<'a> xdrs::ReadXDR<'a>,
39{
40 /// Deserialize and return the stored value.
41 /// Aborts if the key does not exist.
42 #[cfg_attr(not(target_arch = "riscv32"), track_caller)]
43 pub fn get(&self) -> T {
44 use crate::re_throw::OrReThrow;
45 self.try_get().or_re_throw()
46 }
47
48 /// Deserialize and return the stored value, reporting failure as `Err`
49 /// instead of aborting (the `try_` law; see the [`exn`](mod@crate::exn)
50 /// module docs).
51 ///
52 /// The `Err` is exactly the exception [`get`](Self::get) would have
53 /// thrown: a missing key is a `LibraryExn::Syscall` for which
54 /// [`is_storage_missing`](crate::exn::LibraryExn::is_storage_missing)
55 /// is true — use
56 /// [`none_if_missing`](crate::exn::NoneIfMissing::none_if_missing) to
57 /// treat just that case as an ordinary `None`. A value that fails to
58 /// decode as `T` (or exceeds the no-alloc read buffer — either way, the
59 /// slot was written by something other than `Value<T>::set`) is
60 /// `LibraryExn::StorageSerialization`; a value of another storage type
61 /// or any other syscall failure is a `LibraryExn::Syscall`.
62 pub fn try_get(&self) -> Result<T, crate::exn::LibraryExn> {
63 use crate::exn::{LibraryExn, syscall_exn};
64 use crate::sys::SyscallNo;
65 use crate::sys_result::bytestring::bytestring_read_into;
66
67 // Read into the reused in-place scratch and decode an owned value out
68 // of it before the borrow ends — no buffer of our own.
69 crate::call::with_scratch(|buf| {
70 match bytestring_read_into(self.key.get(), buf) {
71 Ok(Some(())) => {}
72 // Stored value exceeds the buffer: only possible if the slot
73 // was written by something other than Value<T>::set.
74 Ok(None) => return Err(LibraryExn::StorageSerialization),
75 Err(e) => {
76 return Err(syscall_exn(SyscallNo::kBytestringGet, e));
77 }
78 }
79 xdrs::cxs::decode(buf.as_slice())
80 .map_err(|_| LibraryExn::StorageSerialization)
81 })
82 }
83}
84
85impl<T> Value<T>
86where
87 T: xdrs::WriteXDR,
88{
89 /// Serialize and store the value immediately.
90 pub fn set(&mut self, value: &T) {
91 use crate::re_throw::OrReThrow;
92 self.try_set(value).or_re_throw()
93 }
94
95 /// Like [`set`](Self::set), but report failure as `Err` instead of
96 /// aborting (the `try_` law; see the [`exn`](mod@crate::exn) module
97 /// docs). A value that fails to serialize is
98 /// `LibraryExn::StorageSerialization`; a syscall failure is
99 /// `LibraryExn::Syscall`. On `Err` the stored value is unmodified.
100 pub fn try_set(
101 &mut self,
102 value: &T,
103 ) -> Result<(), crate::exn::LibraryExn> {
104 self.try_set_impl(value, self.key.is_exclusive())
105 }
106
107 /// Serialize and store the value with an exclusive lock.
108 pub fn set_exclusive(&mut self, value: &T) {
109 use crate::re_throw::OrReThrow;
110 self.try_set_exclusive(value).or_re_throw()
111 }
112
113 /// Like [`set_exclusive`](Self::set_exclusive), but report failure as
114 /// `Err` instead of aborting (the `try_` law; see the
115 /// [`exn`](mod@crate::exn) module docs). The error contract is
116 /// [`try_set`](Self::try_set)'s. On `Err` the stored value is
117 /// unmodified and no lock is taken.
118 pub fn try_set_exclusive(
119 &mut self,
120 value: &T,
121 ) -> Result<(), crate::exn::LibraryExn> {
122 self.try_set_impl(value, true)
123 }
124
125 /// Shared serialize-and-set; `exclusive` selects the lock flavor.
126 fn try_set_impl(
127 &mut self,
128 value: &T,
129 exclusive: bool,
130 ) -> Result<(), crate::exn::LibraryExn> {
131 use crate::exn::LibraryExn;
132 use crate::re_throw::OrSyscallThrow;
133 use crate::sys::SyscallNo;
134 use crate::sys_result::bytestring::*;
135
136 crate::call::with_scratch(|buf| {
137 crate::internal::encode_scratch(buf, value)
138 .map_err(|_| LibraryExn::StorageSerialization)?;
139 let result = if exclusive {
140 bytestring_set_exclusive(&self.key, buf.as_slice())
141 } else {
142 bytestring_set(&self.key, buf.as_slice())
143 };
144 result.or_syscall_exn(SyscallNo::kBytestringSet)
145 })
146 }
147}
148
149impl<T> Value<T>
150where
151 T: xdrs::WriteXDR + for<'a> xdrs::ReadXDR<'a>,
152{
153 /// Load the value and return a mutable reference to it.
154 #[cfg_attr(not(target_arch = "riscv32"), track_caller)]
155 pub fn load_mut(&mut self) -> ValueMut<'_, T> {
156 let value = self.get();
157 ValueMut {
158 value: self,
159 data: value,
160 dirty: false,
161 _live: crate::storage::live_guards::LiveGuard::new(),
162 }
163 }
164}
165
166impl<T> super::flags::Flaggable for Value<T> {
167 fn storage_key(&self) -> &crate::types::StorageKey {
168 self.key.get()
169 }
170}
171
172impl<T> super::deletable::Deletable for Value<T> {
173 fn delete_internal(&mut self, exclusive: bool) {
174 let exclusive = exclusive || self.key.is_exclusive();
175 crate::sys_exn::delete::delete(&self.key, exclusive);
176 }
177}
178
179/// Mutable view into a `Value<T>`. Flushes `T` back to storage on drop if
180/// it was mutated (accessed through `DerefMut`) since the last
181/// [`flush`](Self::flush); a guard that was only read writes nothing.
182pub struct ValueMut<'a, T: __xdrs::WriteXDR> {
183 value: &'a mut Value<T>,
184 data: T,
185 dirty: bool,
186 /// Registers this guard as live for the terminal-return check. Holding
187 /// the field is the registration; on guest builds it is a `Drop`-less
188 /// ZST and compiles away. Any future deferred-write guard should carry
189 /// one too.
190 _live: crate::storage::live_guards::LiveGuard,
191}
192
193impl<T: __xdrs::WriteXDR> ValueMut<'_, T> {
194 /// Write the value back to storage now instead of waiting for drop.
195 ///
196 /// A guard that has not been mutated (no `DerefMut` access) since the
197 /// last flush is a no-op. After a flush the guard remains usable;
198 /// mutating it again re-arms the drop-time writeback.
199 pub fn flush(&mut self) {
200 use crate::re_throw::OrReThrow;
201 self.try_flush().or_re_throw()
202 }
203
204 /// Like [`flush`](Self::flush), but report failure as `Err` instead of
205 /// aborting (the `try_` law; see the [`exn`](mod@crate::exn) module
206 /// docs) — in particular, the only way to catch an error the drop-time
207 /// writeback would abort with. The `Err` is exactly
208 /// [`Value::try_set`]'s. On `Err` the guard still holds the value as
209 /// dirty: drop re-attempts the writeback and aborts if it still fails,
210 /// so a caught error cannot silently lose the write.
211 pub fn try_flush(&mut self) -> Result<(), crate::exn::LibraryExn> {
212 if !self.dirty {
213 return Ok(());
214 }
215 self.value.try_set(&self.data)?;
216 self.dirty = false;
217 Ok(())
218 }
219}
220
221impl<T: __xdrs::WriteXDR> Deref for ValueMut<'_, T> {
222 type Target = T;
223
224 fn deref(&self) -> &T {
225 &self.data
226 }
227}
228
229impl<T: __xdrs::WriteXDR> DerefMut for ValueMut<'_, T> {
230 fn deref_mut(&mut self) -> &mut T {
231 self.dirty = true;
232 &mut self.data
233 }
234}
235
236impl<T: __xdrs::WriteXDR> Drop for ValueMut<'_, T> {
237 fn drop(&mut self) {
238 if self.dirty {
239 self.value.set(&self.data);
240 }
241 // `_live` drops here, decrementing the count.
242 }
243}