Debugging Arc's

I have some fairly in-depth usage with Arc's. I am attempting to debug an Arc getting lost with (probably) some unsafe code (one Arc is not getting dropped causing a memory leak). Is there a way to debug output the specific pointer of different std::sync::Arc's so i could see exactly which one is not getting cleaned up?

For example:

use std::sync::Arc;

fn main() {
    let x = Arc::new(1);
    let y = x.clone();
    
    println!("X: {:?}, Y: {:?}", x, y);
}

Outputs:

X: 1, Y: 1

But i actually want the pointer address. That way i can differentiate between the Arc's. I noticed nightly rust has Arc::as_ptr() but that too gives a pointer to the wrapped struct and not the Arc itself. Any quick tips?

My teammate had a nice idea to use a wrapper struct and add an AtomicU64 to attempt to "mark" each one. Well see how this goes!

Does this do what you want?

use std::sync::Arc;
fn main() {
    let x = Arc::new(1);
    let y = x.clone();
    
    println!("X: {:p}, Y: {:p}", x, y);
}
X: 0x558348f3ea40, Y: 0x558348f3ea40
2 Likes

Sorry, about my earlier reply, I read too quick!

X: 0x558348f3ea40, Y: 0x558348f3ea40 are still the same memory address, i am looking to uniquely identify the different Arc's.

Ah, no, that's not possible with built-in functionality.

The only thing contained within an Arc is a pointer to the inner structure, so they’re literally indistinguishable from each other. You could use the memory address of the Arc itself, but that will change whenever it’s moved from one place to another, like by being returned from a function.

1 Like

Arc is a pointer, similar to Box, and all clones of the same Arc point to the same thing, i.e. have exact same value.

You're probably expecting Arc to have two layers of indirection — one to some Arc object, and then another to the data. This is not the case. There is only one layer of indirection: straight to where the count and the data is stored together in one place.

You could try get an address of that pointer, each individual clone of Arc, but that is futile, because it's like wanting an address of usize. It's not heap allocated, and it doesn't have a stable address.

Your code:

fn main() {
    let x = Arc::new(1);
    let y = x.clone();
    
    println!("X: {:?}, Y: {:?}", x, y);
}

is equivalent of:

fn main() {
    let x = 12345;
    let y = 12345.clone();
    
    println!("X: {:?}, Y: {:?}", 12345, 12345);
}

All clones of Arc are identical. The "clone" part is fake, as it doesn't create anything new, it only updates a counter.

2 Likes

If you have control over when your Arc<T> is created, what about Arc::downgrade()-ing to a Weak<T> pointer and stashing that Weak<T> away somewhere? Potentially using Weak::as_ptr() to get a stable pointer for use as a key.

You can then iterate over all the weak pointers to see which is still alive at some later point in your program.

For future reference: it is sometimes recommended to use Arc::clone(x) instead of x.clone() to emphasize that we're cloning the Arc rather than the underlying data. See clippy lint clone_on_ref_ptr (allowed by default).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.