Does rust print the pointer?

With "c" background, I'm confused with output of below snippet. Doesn't rust print the pointer(for &x)?

fn main() {
let mut x: Box = Box::new(9);
*x +=1;
println!("values: {}/{} {}/{}", x,*x, &x, &*x);
}

Ouput: values: 10/10 10/10

In std::fmt - Rust you can see all the ways you can customize the print behaviour. To get the address of the pointer you need to do

println!("values: {:p}/{} {:p}/{:p}", x,*x, &x, &*x);

note that the second can't be printed as a pointer, because it's type is just an integer

See also the first half of this recent topic.

println! implicitly borrows its arguments anyway to use &dyn Display and support it on value types too, so &x is actually &&x when printed. This is more magic than regular function calls.

Generally Rust doesn't care about addresses of things. Objects are not identified by their address. It's very rare for pointers to be visibly used anywhere.

Rust's Deref and magic behind the . operator tends to make levels of indirection invisible/irrelevant, and peel them to the right thing. (&&&&&&&&&&&&&&&&&&&&&&&&&&x).to_string() is valid, and does the same thing as x.to_string(). This approach is kinda reverse of C++ that is precise about . vs ->, but has reference collapsing rules elsewhere.

There's {:p} for printing pointer-ness of arguments, but it's IMHO useless. You won't learn from it how Rust actually works, because the mere act of asking for an address of something makes Rust work differently (values without an inherent address will be put in a location that can have one, and exposing a pointer will prevent LLVM from optimizing that out).

4 Likes

The docs make me wonder why "p" formatting even exists.

The docs are a bit strong. Yes, values may be moved without notice — by their owners, while they are not borrowed. And local variables, the most common owner, make no guarantees about their address. But you don't use {:p} to print a local variable — you use it to print a pointer value, and that pointer value has its own rules. Box isn’t going to spontaneously move its allocation, and even more so an Rc, Arc, or & you didn't drop and re-create can't change the address it is, because other things are sharing access to that memory. And when you print with {ptr:p} you're asking to print a specific pointer, not the address of a value the compiler is allowed to move.

So, you absolutely can use :p formatting to reliably answer the question: do these two currently-valid pointers point to the same memory or not? That’s rarely a useful question in a correctly working program, but sometimes, in debugging scenarios, it is an utterly necessary one, and :p gives you a way to get at that information that's more convenient than converting whatever pointer type you have to usize and printing that.

But is it actually worth having :p in the language as a piece of the standard formatting machinery, given how rarely it is useful? Perhaps not. It could be a helper function/newtype:

use core::{fmt, ops};

struct PrintAddr<'a, T>(&'a T);

impl<T: ops::Deref> fmt::Debug for PrintAddr<'_, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        write!(f, "{:#016x}", (&raw const *T::deref(self.0)).addr())
    }
}

fn main() {
    let b = Box::new(1);
    println!("{:?}", PrintAddr(&b));
}

On the other hand, now you have the funny business where, to avoid giving up ownership of unique pointers like Box and &mut, you're taking a reference to the pointer being printed. The format string system gets to do that implicitly; format!("{foo:p}") always prints exactly the address that foo is, not the address where foo is stored. I find that elegant.

3 Likes

Thank you every one!

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.