pub struct Tracked<T: Automorph> { /* private fields */ }Expand description
A value loaded from an Automerge document with change tracking.
Tracked<T> wraps a value and remembers where it was loaded from,
allowing you to detect when the document has been modified and efficiently
update your local copy.
§⚠️ IMPORTANT: Understanding has_structural_changes()
has_structural_changes() only detects structural changes, not scalar field changes!
“Structural changes” means changes to nested objects (Vecs, HashMaps, nested structs). Changes to scalar fields (String, integers, bool) within a struct are NOT detected because they don’t change Automerge ObjIds.
Example where has_structural_changes() returns false despite data changing:
#[derive(Automorph)]
struct Person { name: String, age: i64 } // All scalar fields!
let tracked = Tracked::<Person>::load(&doc, &ROOT, "person")?;
// Another user changes person.name from "Alice" to "Bob"
assert!(!tracked.has_structural_changes(&doc)?); // Returns false! No structural change.For reliable change detection, ALWAYS use update() which compares actual values.
§How Structural Change Detection Works
Automerge assigns unique ObjIds to objects (maps and lists). When an object
is added, removed, or replaced, it gets a new ObjId. Tracked<T> caches ObjIds
from when the value was loaded and compares them against current ObjIds.
§When Structural Detection Works
-
Nested objects: When your struct contains other structs, Vecs, or HashMaps, changes to those nested objects ARE detected because they have their own ObjIds.
-
Object replacement: When an entire value is replaced (not just fields modified).
-
Deletion: When a value is deleted from the document.
§Recommended Usage
Use has_structural_changes() as a performance optimization, not for correctness:
// Pattern 1: Quick filter then full check (RECOMMENDED for complex nested types)
if tracked.has_structural_changes(&doc)? {
let changes = tracked.update(&doc)?;
// changes is accurate
}
// Pattern 2: Always update (SAFEST - use this for types with scalar fields)
let changes = tracked.update(&doc)?;
if changes.any() {
// Handle changes
}§Performance
| Operation | Complexity | Reliability |
|---|---|---|
has_changes() | O(1) to O(field count) | May miss scalar changes! |
update() | O(data size) | 100% accurate |
save() | O(changed data size) | 100% accurate |
Implementations§
Source§impl<T: Automorph> Tracked<T>
impl<T: Automorph> Tracked<T>
Sourcepub fn load<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self>
pub fn load<D: ReadDoc>( doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>, ) -> Result<Self>
Sourcepub fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self>
pub fn load_at<D: ReadDoc>( doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>, heads: &[ChangeHash], ) -> Result<Self>
Loads a value from a specific document version and caches its field ObjIds.
§Arguments
doc- The Automerge document to read fromobj- The parent object IDprop- The property name or indexheads- The change hashes representing the document version
§Errors
Returns an error if the value cannot be loaded.
Sourcepub fn has_structural_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool>
pub fn has_structural_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool>
Checks if the document has structural changes compared to this tracked value.
§What “Structural Changes” Means
This method detects changes to the structure of your data:
- Nested objects (structs, Vecs, HashMaps) being added, removed, or replaced
- The value being deleted entirely
- The entire object being replaced with a new one
It does NOT reliably detect changes to scalar fields (String, integers, bool) within a struct, because those don’t change Automerge ObjIds.
§When to Use This Method
Use has_structural_changes() as a performance optimization to skip
expensive update() calls when you know the data structure hasn’t changed:
// Fast path: skip update if no structural changes
if tracked.has_structural_changes(&doc)? {
let changes = tracked.update(&doc)?;
// Handle changes...
}§When NOT to Use This Method
Do not rely on this method for correctness if your structs have scalar
fields that may be modified. Use update() instead, which
always returns accurate change information.
§Performance
This is an O(field count) operation that compares cached ObjIds, without deserializing any values.
§Errors
Returns an error if the document cannot be read.
§See Also
update()- Always accurate change detectioncursor_diff()- Structural change details
Sourcepub fn might_have_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool>
👎Deprecated since 0.2.0: Renamed to has_structural_changes() to clarify that it only detects structural changes (nested objects), not scalar field changes.
pub fn might_have_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool>
Deprecated: Use has_structural_changes() instead.
Sourcepub fn has_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool>
👎Deprecated since 0.1.0: Renamed to has_structural_changes() to clarify that it only detects structural changes.
pub fn has_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool>
Deprecated: Use has_structural_changes() instead.
Sourcepub fn cursor_diff<D: ReadDoc>(
&self,
doc: &D,
) -> Result<<T::Cursor as FieldCursor>::Changes>
pub fn cursor_diff<D: ReadDoc>( &self, doc: &D, ) -> Result<<T::Cursor as FieldCursor>::Changes>
Gets a detailed hierarchical change report based on ObjId comparison.
§⚠️ WARNING: Only Detects Structural Changes!
Like has_structural_changes(), this method compares ObjIds,
not actual values. It may report no changes even when scalar fields
have been modified.
For reliable change detection, use update() instead.
§Performance
This is an O(field count) operation that compares cached ObjIds, returning a detailed report of which fields changed.
§Errors
Returns an error if the document cannot be read.
Sourcepub fn update<D: ReadDoc>(&mut self, doc: &D) -> Result<T::Changes>
pub fn update<D: ReadDoc>(&mut self, doc: &D) -> Result<T::Changes>
Updates the value from the document and returns a change report.
This method walks each field individually and only updates fields that
have actually changed. It does NOT call load().
This also refreshes the cached ObjIds for future comparisons.
§Errors
Returns an error if the update fails.
Sourcepub fn get_mut(&mut self) -> &mut T
pub fn get_mut(&mut self) -> &mut T
Returns a mutable reference to the inner value.
Note: After modifying the value, you should call save to persist
changes to the document.
Sourcepub fn into_inner(self) -> T
pub fn into_inner(self) -> T
Consumes the wrapper and returns the inner value.
Trait Implementations§
Auto Trait Implementations§
impl<T> Freeze for Tracked<T>
impl<T> RefUnwindSafe for Tracked<T>
impl<T> Send for Tracked<T>
impl<T> Sync for Tracked<T>
impl<T> Unpin for Tracked<T>
impl<T> UnwindSafe for Tracked<T>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more