[][src]Struct topo::CallId

pub struct CallId { /* fields omitted */ }

Identifies the scope of a nested function call in a way that can be deterministically reproduced across multiple executions.

The CallId::current for a function call is the combined product of:

  • a callsite: the std::panic::Location where the function was called
  • a parent: the CallId::current which was active when calling the function
  • a slot: a value indicating the call's "index" within the parent call

When a nested call returns or unwinds, it reverts CallId::current to the parent CallId.


use topo::{call, root, CallId};

let returns_two_ids = || {
    let first = call(|| CallId::current());
    let second = call(|| CallId::current());
    assert_ne!(first, second, "these are always distinct calls");
    (first, second)

// running the closure as a nested call(...) gives different siblings
assert_ne!(call(returns_two_ids), call(returns_two_ids));

// a call to root(...) gives each of these closure calls an identical parent CallId
assert_eq!(root(returns_two_ids), root(returns_two_ids));


Every CallId is created by calling one of:


Slots are used to differentiate between repeated calls at the same callsite and define the "index" of a child call within its parent. By default (and in call) the slot is populated by the number of times the current callsite has been called in this parent. Users can provide their own slot with call_in_slot or using #[topo::nested(slot = "...")]:

See call_in_slot and nested for examples.


The topmost parent or "root" of a callgraph can be defined in two ways:

  1. a call or call_in_slot invocation with no parent implicitly creates its own root
  2. an explicit call to root creates a new subgraph regardless of the current parent

See root for examples.

CallId and multiple threads

The illicit environment used for tracking the current CallId is thread-local, but values used to track slots are interned in a global cache. This means that two different threads calling an identical chain of nested functions can observe identical CallIds:

use std::{
    sync::mpsc::{channel, Sender},

let (send_ids, recv_ids) = channel();

let spawn_worker = |sender: Sender<(CallId, CallId)>| {
    thread::spawn(move || sender.send(root(returns_two_ids)).unwrap())
let first_thread = spawn_worker(send_ids.clone());
let second_thread = spawn_worker(send_ids);


// the two worker threads "did the same work"
assert_eq!(recv_ids.recv()?, recv_ids.recv()?);


impl CallId[src]

pub fn current() -> Self[src]

Returns the current CallId.

Trait Implementations

impl Clone for CallId[src]

impl Copy for CallId[src]

impl Debug for CallId[src]

impl Eq for CallId[src]

impl Hash for CallId[src]

impl PartialEq<CallId> for CallId[src]

impl StructuralEq for CallId[src]

impl StructuralPartialEq for CallId[src]

Auto Trait Implementations

impl RefUnwindSafe for CallId

impl Send for CallId

impl Sync for CallId

impl Unpin for CallId

impl UnwindSafe for CallId

Blanket Implementations

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

impl<T> AsContext for T where
    T: Debug + 'static, 

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

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

impl<T> Downcast for T where
    T: Any

impl<T> DowncastSync for T where
    T: Send + Sync + Any

impl<T> Erased for T

impl<T> From<T> for T[src]

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

impl<T> ToOwned for T where
    T: Clone

type Owned = T

The resulting type after obtaining ownership.

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

type Error = Infallible

The type returned in the event of a conversion error.

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

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

The type returned in the event of a conversion error.