Skip to main content

Tracked

Struct Tracked 

Source
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

  1. Nested objects: When your struct contains other structs, Vecs, or HashMaps, changes to those nested objects ARE detected because they have their own ObjIds.

  2. Object replacement: When an entire value is replaced (not just fields modified).

  3. Deletion: When a value is deleted from the document.

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

OperationComplexityReliability
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>

Source

pub fn load<D: ReadDoc>( doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>, ) -> Result<Self>

Loads a value from the document and caches its field ObjIds.

§Arguments
  • doc - The Automerge document to read from
  • obj - The parent object ID
  • prop - The property name or index
§Errors

Returns an error if the value cannot be loaded.

Source

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 from
  • obj - The parent object ID
  • prop - The property name or index
  • heads - The change hashes representing the document version
§Errors

Returns an error if the value cannot be loaded.

Source

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
Source

pub 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.

Deprecated: Use has_structural_changes() instead.

Source

pub 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.

Deprecated: Use has_structural_changes() instead.

Source

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.

Source

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.

Source

pub fn save<D: Transactable + ReadDoc>(&self, doc: &mut D) -> Result<()>

Saves changes back to the document.

§Errors

Returns an error if the save operation fails.

Source

pub fn get(&self) -> &T

Returns a reference to the inner value.

Source

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.

Source

pub fn into_inner(self) -> T

Consumes the wrapper and returns the inner value.

Trait Implementations§

Source§

impl<T: Clone + Automorph> Clone for Tracked<T>
where T::Cursor: Clone,

Source§

fn clone(&self) -> Tracked<T>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<T: Debug + Automorph> Debug for Tracked<T>
where T::Cursor: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<T: Automorph + Default> Default for Tracked<T>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<T: Automorph> Deref for Tracked<T>

Source§

type Target = T

The resulting type after dereferencing.
Source§

fn deref(&self) -> &Self::Target

Dereferences the value.

Auto Trait Implementations§

§

impl<T> Freeze for Tracked<T>
where T: Freeze, <T as Automorph>::Cursor: Freeze,

§

impl<T> RefUnwindSafe for Tracked<T>

§

impl<T> Send for Tracked<T>
where T: Send, <T as Automorph>::Cursor: Send,

§

impl<T> Sync for Tracked<T>
where T: Sync, <T as Automorph>::Cursor: Sync,

§

impl<T> Unpin for Tracked<T>
where T: Unpin, <T as Automorph>::Cursor: Unpin,

§

impl<T> UnwindSafe for Tracked<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<P, T> Receiver for P
where P: Deref<Target = T> + ?Sized, T: ?Sized,

Source§

type Target = T

🔬This is a nightly-only experimental API. (arbitrary_self_types)
The target type on which the method may be called.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more