Help with this syntax

I'm not sure what this means. Did I create reference to static object with lifetime static ?
Can I use this for non const initializations ?

const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4];

This is equivalent to.

const INDICES_ARR: [u16; 9] = [0, 1, 4, 1, 2, 4, 2, 3, 4];
const INDICES: &[u16] = &INDICES_ARR;

Note that this is not a static object, rather constants are repeated on every use. If you want a static object that is only stored once, use static.

static INDICES_ARR: [u16; 9] = [0, 1, 4, 1, 2, 4, 2, 3, 4];
static INDICES: &[u16] = &INDICES_ARR;
1 Like

It kind-of is a static object though, internally. It doesn't appear to be the case that the array itself is duplicated to each use of the const. E.g.:

const FOO: &[u8] = &[1,2,3];

fn main() {
    println!("{:p}", FOO);
    foo();
}

fn foo() {
    println!("{:p}", FOO);
}

prints the same address twice. Of course this should be true nonetheless:

But the second const definition, INDICES, does define a static object, as far as I understand it. Of course there are restrictions that make it almost impossible to observe that there's a single static object behind all uses of FOO or INDICES: It is impossible to create a reference to a const that contains internal mutability. Checking for pointer value / equality is pretty much the only way you can observe that you are re-using the same object. And also one should probably not rely on this. This is similar to how equal string constants are unified to be the same static value.

On the point of interior mutability,
e.g. this does not compile:

use once_cell::sync::Lazy;
use std::sync::Mutex;

const FOO: &Lazy<Mutex<u8>> = &Lazy::new(|| Mutex::new(0));

And this doesn't either:

const BAR: Lazy<Mutex<u8>> = Lazy::new(|| Mutex::new(0));

const FOO: &Lazy<Mutex<u8>> = &BAR;

Apparently this doesn't work either (though, in my opinion it would actually make sense to allow this):

const BAR: Lazy<Mutex<u8>> = Lazy::new(|| Mutex::new(0));
static BAZ: Lazy<Mutex<u8>> = BAR;
const FOO: &Lazy<Mutex<u8>> = &BAZ;

This works however:

const BAR: Lazy<Mutex<u8>> = Lazy::new(|| Mutex::new(0));
static BAZ: Lazy<Mutex<u8>> = BAR;
static FOO: &Lazy<Mutex<u8>> = &BAZ;
1 Like

While we're on the topic:

const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4];

When should we accept this as good enough, and when should we say "no, this is a fixed size array, just be an array" and define it as [u16; 9]? What are the trade-offs?

An ordinary reference to a slice &[u16] is 16 bytes because it contains the length. An &[u16; 9] is 8 bytes because the length is part of the type.

Besides that, there are not really much difference unless you want to pass it to function that specifically takes [u16; 9].

So handling raw arrays isn't necessarily more efficient than handling slices?

No, that would depend on the exact case. It's not like slices come with a large overhead.

2 Likes

I do trust slices in my heart, but was curious if there were hidden secrets to be unlocked by using raw arrays. Thanks, I'll continue to use slices freely.

It's more of a semantic question. If I were to embed some constant data in my executable that had to be of a certain size (for example because it is specified by some protocol), then I would certainly continue to use an array. The reason is that you can go from an array to a slice unconditionally, but the converse is not true. (Nowadays there's impl TryFrom<&[T]> for &[T; N] but that's kind of clunky – why throw away the size and re-create it then unwrap() if you already knew it in the first place?)

So if I wanted to ensure the size, I'd do it at compile time. Say I was implementing a hash function. I wanted to ensure with some test cases that its output matches the specs. Then I would make my test cases [u8; 64], because I can then ensure that if the code compiles, then whatever I'm comparing my output against has the right size.

1 Like

Under certain circumstances you could unlock some optimisations because the compiler can guarantee that a &[u16; 9] has 9 elements but the same can't be said of &[u16]. That means passing around that &[u16; 9] might not incur as many bounds checks.

I doubt this would happen in practice though because LLVM is smart enough to know when you are using values known at compile time and infer the same information from them (e.g. array length).

That said, any actual effects on optimisation would be negible and you should focus on @H2CO3's argument about writing code which matches the situation and is readable. Hash functions have specific block sizes so you might pass around &[u8; 64], while a normal array of numbers would be a &[u8] because the length isn't really important.

2 Likes

Taking a reference to a const array at runtime also prints the same address twice:

const BAR: [u8; 3] = [1,2,3];

fn main() {
    println!("{:p}", &BAR);
    foo();
}

fn foo() {
    println!("{:p}", &BAR);
}
0x55c18fe84001
0x55c18fe84001

I feel a little out of my depth here, but the array is as you said, internally a static location in the compiled binary. So printing the address of this array will always give the same "virtual" memory address that points to that specific place in the binary.
While it's not declared with static, this reference can have a 'static lifetime so it feels like a static item.

This also prints the same address twice.

fn main() {
    println!("{:p}", &[1,2,3]);
    foo();
}

fn foo() {
    println!("{:p}", &[1,2,3]);
}
4 Likes

Here is an example of the difference between const and static items: https://github.com/rust-lang/rust/issues/77983

I believe that the only const thing here is the reference itself, the array is in the static part of executable , there is no other way

I think the array is still allowed to be duplicated. At least I am pretty sure that it will currently when you access it from two different modules due to https://github.com/rust-lang/rust/issues/79738. However, because the array is immutable, it will be placed in .rodata, which gets the MERGE flag on at least ELF systems, so the linker will deduplicate it anyway. (if you don't use dylibs at least)

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.