Skip to main content

SavepointId

Struct SavepointId 

Source
pub struct SavepointId<'t> { /* private fields */ }
Available on crate features alloc or std only.
Expand description

An opaque handle to one savepoint inside a StackedTransaction.

Returned by savepoint and consumed by rollback_to and release. It is a small Copy token that holds no runtime borrow, so it can be stashed in a list of candidates or returned up the call stack while the transaction stays open.

§Not a durable position token

A SavepointId is branded with the lifetime of the transaction that issued it, so it cannot outlive that transaction or the input loan behind it — keeping one past the parse is a compile error, not a dangling handle. For a position that must survive beyond the transaction, capture a Cursor or a span instead.

§How a misused id is caught

Identity is layered, from compile time down to a runtime check, with no global state — no counter, no atomic — behind any of it:

  • Temporal misuse — using an id after its transaction ended, or holding one across the next begin_stacked on the same input — is a compile error: the id’s invariant lifetime brand keeps the input loan open while the id is live, so the borrow checker rejects reopening it.
  • Cross-parser misuse — an id from another, simultaneously-live transaction over a different input — panics in every build: each id carries the address of an Input-owned field, and two live inputs occupy distinct addresses.
  • Intra-transaction staleness — an id destroyed by an earlier rollback_to / release on the same transaction, or one whose checkpoint a raw restore below it invalidated (see the mixing rules on StackedTransaction) — panics in every build via a membership scan of the live savepoints and their lineage (see rollback_to). State surgery is transactional and does not invalidate a savepoint.

§Compile-time rejections

The temporal and nesting misuses never reach a runtime check — the borrow checker rejects them. Each snippet below fails to compile.

Reusing an id after its transaction ended (here, across the next begin_stacked on the same input) — the id keeps the first transaction’s loan on input open, so the second begin_stacked cannot re-borrow it:

use tokora::{InputRef, Lexer, ParseContext};

fn temporal_misuse<'inp, L, Ctx>(input: &mut InputRef<'inp, '_, L, Ctx>)
where
  L: Lexer<'inp>,
  L::State: Clone,
  Ctx: ParseContext<'inp, L>,
{
  let sp = {
    let mut txn = input.begin_stacked();
    let sp = txn.savepoint();
    txn.commit();
    sp
  };
  let mut txn2 = input.begin_stacked(); // error[E0499]: `input` is still borrowed by `sp`
  txn2.rollback_to(sp);
}

Storing an id past the parse (returning it out of the transaction) — the brand cannot outlive the input loan:

use tokora::{InputRef, Lexer, ParseContext, SavepointId};

fn durable_misuse<'inp, 'closure, L, Ctx>(
  input: &mut InputRef<'inp, 'closure, L, Ctx>,
) -> SavepointId<'closure>
where
  L: Lexer<'inp>,
  L::State: Clone,
  Ctx: ParseContext<'inp, L>,
{
  let mut txn = input.begin_stacked();
  let sp = txn.savepoint();
  txn.commit();
  sp // error: the id's brand does not outlive the transaction's loan
}

Passing a parent savepoint into a nested child transaction — the parent’s brand region strictly contains the child’s (the parent is used before the child exists), so the invariant brands cannot unify:

use tokora::{InputRef, Lexer, ParseContext};

fn parent_id_in_child<'inp, L, Ctx>(input: &mut InputRef<'inp, '_, L, Ctx>)
where
  L: Lexer<'inp>,
  L::State: Clone,
  Ctx: ParseContext<'inp, L>,
{
  let mut parent = input.begin_stacked();
  let sp_parent = parent.savepoint();
  let mut child = parent.begin_stacked();
  child.rollback_to(sp_parent); // error[E0597]: brands cannot unify parent with child
}

The mirror — keeping a child savepoint to use in the parent after the child ends — also fails: the child id keeps the child’s borrow of the parent alive, so the parent cannot be used:

use tokora::{InputRef, Lexer, ParseContext};

fn child_id_in_parent<'inp, L, Ctx>(input: &mut InputRef<'inp, '_, L, Ctx>)
where
  L: Lexer<'inp>,
  L::State: Clone,
  Ctx: ParseContext<'inp, L>,
{
  let mut parent = input.begin_stacked();
  let sp_child = {
    let mut child = parent.begin_stacked();
    let s = child.savepoint();
    child.commit();
    s
  };
  parent.rollback_to(sp_child); // error: `parent` is still borrowed by the child id
}

Trait Implementations§

Source§

impl<'t> Clone for SavepointId<'t>

Source§

fn clone(&self) -> SavepointId<'t>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

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

Performs copy-assignment from source. Read more
Source§

impl<'t> Copy for SavepointId<'t>

Source§

impl<'t> Debug for SavepointId<'t>

Source§

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

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

impl<'t> Eq for SavepointId<'t>

Source§

impl<'t> PartialEq for SavepointId<'t>

Source§

fn eq(&self, other: &SavepointId<'t>) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<'t> StructuralPartialEq for SavepointId<'t>

Auto Trait Implementations§

§

impl<'t> Freeze for SavepointId<'t>

§

impl<'t> RefUnwindSafe for SavepointId<'t>

§

impl<'t> Send for SavepointId<'t>

§

impl<'t> Sync for SavepointId<'t>

§

impl<'t> Unpin for SavepointId<'t>

§

impl<'t> UnsafeUnpin for SavepointId<'t>

§

impl<'t> UnwindSafe for SavepointId<'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<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> 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 = !

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

fn try_from(value: U) -> Result<T, !>

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.