What might be wrong with a const reference to a static item?

struct S (pub i32);
static X: S = S(0);
const Y: &'static S = &X;

<anon>:4:24: 4:25 error: constants cannot refer to other statics, insert an intermediate constant instead [E0013]
<anon>:4 const Y: &'static S = &X;

Taking a reference out of a static item and passing it around should be safe, shouldn't it? Isn't Rust overreacting here?

1 Like

The error check was introduced in PR21744, but there's no commentary there or anywhere else I can find about why the restriction was imposed, so I have to assume "it made the implementation easier". Constant expressions are known to have a lot of rough edges; I don't know offhand whether this is one of the things that would be addressed in the ongoing MIR work.

Workaround time: What is your use case? Could you use a const instead, as the error message suggests? If not, how about fn y() -> &'static S { &X }? (you might want #[inline] as well if you're planning to access it across crates, I'm unfamiliar with the precise rules.)

So you're suspecting Rust is overreacting here to make the implementation simpler? Fair enough.

Well, I just came up with this weirdness when I was thinking what would be the best interface to model extern { fn __errno_location() -> *mut c_int; } Some people aren't happy with fn set_errno(c_int) because they've never seen such a name in the C language spec.

errno isn't a &'static c_int though. It's thread local on every thread-supporting implementation I've seen, which means its lifetime is limited to the thread; although it's usually not a standard thread local (I would suspect because most implementations predate the standard __thread storage class by a wide margin, but I haven't done the research to confirm this). So you could have a set_errno, or you could have a with_errno that passes an existential lifetime to a callback. I think set_errno is better. I'm not seeing much of precedent for non-C languages setting errno (from a "set errno" search), although interestingly the Windows CRT seems to have a _set_errno function.

Well, if there were not TLS, __errno_location() shouldn't need to exist from the beginning. So I've never said I would use &'static c_int for it.

This is totally off-topic, but if you like set_errno(), it would be great to hear your opinion at this thread, otherwise it won't go anywhere. For some of C APIs, I think clearing errno beforehand is the only way for catching errors from them, so this is a serious issue.
https://github.com/rust-lang/libc/issues/47

OK, I replied there.

I'm not a compiler guy so this could be partially or completely wrong, but const can't refer to statics at all simply because statics don't have addresses yet when const-expansion occurs. This is because, while they seem to be roughly equivalent constructs, const is an abstraction while static is a concrete memory location.

Every use of a const expands to its rvalue, including when used as initialization in other consts. Whereas every static gets its own unique location in memory that is initialized during translation. You can see this difference when trying to take a const and a static by-value: Zerbertz – Dubai, UAE

You can re-use the const by-value as many times as you like because it's essentially a copy-paste, whereas you can't use the static by-value because you're trying to move from a location in memory.

You can take references to consts in other consts and statics because they all const-expand to rvalues which will get filled in during translation, and you can take references to statics in other statics because they get filled in during translation, but you can't reference statics in consts because they don't have a memory location yet.

Have a look at this modification of your original example: Rust Playground

If you run it, you'll see the same pointer value gets printed for all three expressions, because they all reference the same S(0) rvalue.

Whereas in this version: Rust Playground

&X and &Y reference unique locations in the data segment; interestingly, with a 12 byte offset. I wonder what the compiler is putting between those two. Doesn't matter for our purposes, but interesting nonetheless.

1 Like