Static or const

CanisterStableMemory is defined so:

struct CanisterStableMemory {}

I want to define zero-size reference to CanisterStableMemory, because CanisterStableMemory can have no more than one value (all values are equal):

/// It's advantage over `&CanisterStableMemory` is that `CanisterStableMemoryRef` is zero-size.
struct CanisterStableMemoryRef;

impl Deref for CanisterStableMemoryRef {
    type Target = CanisterStableMemory;

    fn deref(&self) -> &Self::Target {
        static value: CanisterStableMemory = CanisterStableMemory {}; // or `const`?
        &value
    }
}

Should I use static value or const value and what is the difference?

You don't need either static or const here due to rvalue static promotion: playground.

2 Likes

Maybe I just don't get it, but this seems like an unusual use of Deref, what are you trying to accomplish by "throwing away all the fields of CanisterStableMemory" when you deref?

Yes, because CanisterStableMemory has zero fields.

Ah, I thought you were just omitting the fields.
Why have CanisterStableMemoryRef if they're effectively the same?

A static is a specific fixed place in memory, which is initialized on program startup. In particular, it has a well-defined address, so that it can be referenced, and those references can be accessed as usual.

A const isn't a variable or a place in memory. It is a compile-time computation. Let's say you define

const FOO: Bar = { .. };

Whenever you use FOO in code, its value is inlined at the call site. A fresh variable will be created for it if requried. So, for example, this assertion may fail:

const FOO: Bar  = { .. };
let a = &FOO;
let b = &FOO;
// Fails depending on the way the optimizer simplifies your code.
assert_eq!(a as *const FOO, b as *const FOO);

But if you use a static variable, the assertion is guaranteed to be true, because you take twice the address of the same variable:

static FOO: Bar  = { .. };
let a = &FOO;
let b = &FOO;
// This never panics.
assert_eq!(a as *const FOO, b as *const FOO);

The distinction is somewhat muddied, since const and static objects act very similarly most of the time. A static variable must be initialized with a const expression --- just like a const declaration. Also, for ergonomic reasons, expressions are sometimes implicitly promoted to a static, even if they would normally create a local variable. This functions uses a const, but has the same behaviour as if it used a static:

const FOO: Bar = { .. };

fn quux() -> &'static Bar {
    &FOO
}

The compiler creates a local variable to hold the value of FOO, and then promotes it to an unnamed static variable. This implies that quux always returns the same address. However, static promotion is performed for each function individually. Thus the following assertion may fail:

fn baz() -> &'static Bar { &FOO }
// Will likely fail, because each function makes its own static.
assert_eq!(baz() as *const Bar, quux() as *const Bar);

As a rule of thumb, you should generally prefer constants, unless you specifically need a static variable. E.g. if you need something with a 'static lifetime, accessible at any point in the program, use a static. If you need to interoperate with foreign code over FFI, use a static (const doesn't exist over FFI, since it doesn't represent a place in memory). If you need to mutate some global data, use a static (or a static mut in the extremely rare case that you want to do it safely, but in a way which cannot be safely expressed in Rust; that's almost never the case).

3 Likes

That said, I can't help but wonder: what are you trying to achieve? The thing you're trying to do doesn't seem to make much sense. You're not creating a "zero-sized reference", you are creating an entirely separate zero-sized type, which acts kinda like a reference with respect to method resolution only. It's not a reference. Nothing prevents the user of your API from creating ordinary references to either CanisterStableMemory or CanisterStableMemoryRef, and those references won't be zero-sized. Moreover, while CanisterStableMemory indeed has a unique value, it doesn't mean that it has a unique address. In fact, I can create a valid CanisterStableMemory at any non-null well-aligned address, and the compiler may very well do so in practice.

So, what is you goal?

1 Like

I use CanisterStableMemoryRef as a generic argument, because other users of my software may choose &SomethingOtherThanCanister instead of CanisterStableMemoryRef.

CanisterStableMemory is an existing zero-sized type. I need to pass this type to my functions.

So, I declare trait Memory and implement it for CanisterStableMemory.

If I'd pass impl Memory to my functions, then I would probably create troubles for the case if somebody creates another type implementing Memory, as it would probably mean passing a heavy-weight type as an argument. Worse, it would be owned not borrowed, as I want.

So, I want not the zero-sized type itself, but zero-sized reference to zero-sized type.

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.