Struct topo::CallId [−][src]
pub struct CallId { /* fields omitted */ }Expand description
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::Locationwhere the function was called - a parent: the
CallId::currentwhich 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.
Example
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));Creation
Every CallId is created by calling one of:
- a function marked
nested - a function passed to
call - a function and slot passed to
call_in_slot
Slots
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.
Roots
The topmost parent or “root” of a callgraph can be defined in two ways:
- a
callorcall_in_slotinvocation with no parent implicitly creates its own root - an explicit call to
rootcreates 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},
thread,
};
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);
first_thread.join().unwrap();
second_thread.join().unwrap();
// the two worker threads "did the same work"
assert_eq!(recv_ids.recv()?, recv_ids.recv()?);Implementations
Trait Implementations
Auto Trait Implementations
impl RefUnwindSafe for CallId
impl UnwindSafe for CallId
Blanket Implementations
Mutably borrows from an owned value. Read more
impl<T> Downcast for T where
T: Any,
impl<T> Downcast for T where
T: Any,
Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait. Read more
pub fn into_any_rc(self: Rc<T>) -> Rc<dyn Any + 'static>
pub fn into_any_rc(self: Rc<T>) -> Rc<dyn Any + 'static>
Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait. Read more
Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s. Read more
pub fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
pub fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s. Read more