Why don't ZSTs have unique addresses?

If every ZST had a different address, then fat pointers could be checked for equality as though they were thin pointers, which would be faster. (The only exception would be where one pointer might be a partial borrow of the other -- either a slice prefix or a struct field with offset zero -- but the pointer types would rule that out in most cases.) So why doesn't Rust do this?

How are fat pointers related to ZSTs? Are you talking about each ZST instance, or each different ZST type? How would you give each ZST a unique address?

Counterexample:

let x = vec![(); usize::MAX];
let y = vec![(); usize::MAX];
7 Likes

That would make them non-zero-size:

struct OST {
   a: (),  b: (),
}

C++ had a guaranteed address identity, and to get ZSTs it had to add an attribute to revert that:

https://en.cppreference.com/w/cpp/language/attributes/no_unique_address


Additionally, many LLVM optimizations require not having address exposed, so pointer comparisons can be expensive and inhibit optimizations.

Rust's niche optimizations for things like Result<()> would be much more difficult if that () had to have an identity.


BTW, today I've stumbled into a rabbit hole of stack pointer comparisons in LLVM. LLVM has two contradictory features:

  1. Assumes that every stack variable has a unique address
  2. Has an optimization pass that eliminates stack copies and reuses stack space, breaking assumption 1.
7 Likes

I'm talking about dyn pointers - they might point to ZST singletons which have the same address because they have the same alignment, but then have different vtable pointers.

Might be helpful to give an example. Are you talking about something like this?

struct Foo;
struct Bar;

trait Run {}

impl Run for Foo {}
impl Run for Bar {}

let v: Vec<Box<dyn Run>> = vec![Foo, Bar, Foo];

let a: &dyn Run = &*v[0];
let b: &dyn Run = &*v[1];
let x: &dyn Run = &*v[2];

assert!(!ptr_addr_eq(a, b));
assert!(ptr_addr_eq(a, c)); // or maybe even these should be different
1 Like

You can't count on vtable pointer equality or inequality either.

note that comparing trait object pointers (*const dyn Trait) is unreliable: pointers to values of the same underlying type can compare inequal (because vtables are duplicated in multiple codegen units), and pointers to values of different underlying type can compare equal (since identical vtables can be deduplicated within a codegen unit).

3 Likes

@quinedot That's fine, because the deduped vtables would have to code for the same behavior. For my purposes, equivalent singletons are equal.

Yes, that's what I'm talking about, except that the trait would have a polymorphic method.

If it were to be required that all zero-sized types have unique addresses for their instances, then:

  • The compiler and linker would have to somehow assign these unique addresses, when the type is monomorphized if it is generic, even though that happens in separate compiler invocations when compiling multiple crates.

  • There would have to be a special case that whenever a ZST pointer is produced from a struct, slice, stack variable, etc. its pointer value is replaced with the unique one.

    (I suppose this is not strictly necessary, because one could say that only literals of the ZST have this unique address, but then it's much less useful as a guarantee. As I see it, an important factor of how ZSTs work is that they are just like non-zero-sized values except that you can do more things with them. In particular, they are movable just like other values.)

3 Likes