How to get the current stack frame depth in Rust?

Viewed 303

How do I determine the current stack frame depth?

fn print_depths() {
    println!("stack frame depth {}", stack_frame_depth());
    fn print_depth2() {
        println!("stack frame depth {}", stack_frame_depth());
    }
    print_depth2();
}

pub fn main() {
    print_depths();
}

would print

stack frame depth 1
stack frame depth 2

I know main has callers so the particular numbers might be different.


I have tried stacker::remaining_stack. However, that prints a count of bytes remaining in the stack. Since the arguments passed to a function affect the stack byte "height" then it is not possible to derive a simple function call "height". I want a count of the current function call height.

2 Answers

After optimisations, the code that actually runs is very unlikely to resemble in any way the structure of functions that you originally wrote.

If Rust exposed a function to give you the number of call frames on the stack, the number would vary hugely, depending on optimisation settings and the exact contents of these functions. For example, adding a debug print statement could change if it is inlined or not. The number could be different depending on if the build is debug or release, if lto is enabled or even if you upgraded to a newer compiler.

In order to accurately track the number of function calls, corresponding to functions in your source code, the compiler would need to add a counter increment to every function body, breaking Rust's promise of zero cost abstraction.

You could write a macro for declaring functions, that increments a counter at the start of each function and decrements it at the end. Then you pay for what you use. For example, here is a simplified (doesn't support generics) declarative macro which does this. A procedural macro would be better for something like this, and you could define it to be called as an attribute instead.

If, having noted all the caveats in Peter Hall's answer, it is still desirable to obtain the actual stack depth, consider backtrace::Backtrace:

#[inline]
fn stack_frame_depth() -> usize {
    Backtrace::new_unresolved().frames().len()
}

See it on the playground.

If you want to avoid the allocation involved in instantiating a Backtrace, consider backtrace::trace instead:

#[inline]
fn stack_frame_depth() -> usize {
    let mut depth = 0;
    backtrace::trace(|_| {
        depth += 1;
        true
    });
    depth
}

Both of the above approaches require std for thread-safety, however. If your crate is no_std and/or you are ensuring synchronization some other way, there's backtrace::trace_unsynchronized.

Related