I am trying to translate some old embedded C codes to Rust, but I meet with some problems.
When I was using C, I can use the reference (e.g. arr
, when arr
is an array) to another variable to initialize a global variable. For example:
#define MASK 0xffff0000
int arr[128] = {0};
uint64_t arr_table[2] = {arr MASK, 0};
I have to declare them at the compile time, so that they can be accessed from extern libraries (e.g. kernel assembly codes).
Is there any equivalent in Rust?
What I have tried
pub static arr: [u32; 128] = [0; 128];
pub static arr_table: [u64; 2] = [&arr as *const [u32; 128] as u64 MASK, 0];
The compiler told me:
error: pointers cannot be cast to integers during const eval
note: at compile-time, pointers do not have an integer value
note: avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior
I have also tried mem::transmute()
to do an unsafe conversion to u64
, and the compiler complaint the same thing:
error[E0080]: could not evaluate static initializer
help: this code performed an operation that depends on the underlying bytes representing a pointer
help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
Question
I know it is impossible for the compiler to know the exact address of a static variable at the compile time.
I am not so familiar with the mechanism behind compilation, but I suppose that C has put off the work till link process and implied the linker to give the right values to arr_table
. I wonder if it is possible to do the same thing in Rust? Thanks!
CodePudding user response:
I was able to get this to build on stable, but there was a fair amount of compiler fight. The short version is that this happens because you're trying to write C-like code in rust that the borrow checker fundamentally can't check, so you are somewhat fighting the language by doing this. You will still be able to do it but the unsafe
keyword will have to be used in declaring and in using variables like this.
I think it would be much better if you can just store normal rust references instead of pointers.
Nevertheless, this version which is closer to what you originally wrote will build:
const MASK: isize = 42;
#[repr(C)]
pub struct MyU32Ptr(*const u32);
unsafe impl Sync for MyU32Ptr {}
pub static ARR: [u32; 128] = [0; 128];
pub static ARR_TABLE: [MyU32Ptr; 2] = [MyU32Ptr(unsafe { (&ARR as *const u32).offset(MASK) }), MyU32Ptr(core::ptr::null())];
The main ideas here are:
*const u32
does not implementSync
, it's not considered safe to share this value across threads. This is because while rust references have lifetimes and language rules prevent that a&
and a&mut
to the same variable can exist at the same time, pointers don't have any of that and represent an escape hatch away from those constraints. The consequence of that is that a lot of things you would want to do with pointers are unsafe. Passing a pointer from one thread to another, or creating a pointer that all threads can see, has to be unsafe, or else it would be impossible for any thread to create a&mut
to do that data ever safely.To make this build anyways, what I did is implement a new-type wrapper around
*const u32
which I calledMyU32Ptr
, gave itRepr(C)
so that it is guaranteed by the language to be the same ABI as a C pointer, and then I just declared that this type isSync
, usingunsafe impl Sync
. That is dirty, but in our case, we may feel okay doing this, knowing thatMyU32Ptr
won't be used to point to anything that is changing at runtime anyways.I replaced your integers with
*const u32
, and your pointer arithmetic with a call to.offset
which is in my understanding the more recommendable way to do this nowadays (see the "strict provenance" tracking issue if you are interested). Since this function is unsafe we have to add an unsafe block when we call it.
It seemed to me that logically you wanted these to be pointers to arrays and it wasn't important to make them integers. I think you can make the calling code convert them to integers if you actually need that for some reason.
I think the most idiomatic way to have tables of arrays like you are doing here right now is to use references and not pointers.
pub static ARR: [u32; 128] = [0; 128];
pub static ARR_TABLE: [&[u32]; 1] = [&ARR];
pub static ANOTHER_ARR_TABLE: [Option<&[u32]>; 2] = [Some(&ARR), None];
(Note that in rust, Option<&T>
has the same in-memory representation as * const T
, it's just that it's more idiomatic in rust than having "maybe null pointers", and it can be used and passed around threads by safe code, because it is an honest borrow-checked reference.)
However, I wasn't able to get the MASK
part to work, you get errors where the compiler complains that "slice" indexing is not a const function.
I think this is related to this rustlang issue: https://github.com/rust-lang/rust/issues/73083
You might be able to get away with like, references to u32s instead of slices, [Option<&u32>; 2]
if that can meet your needs. I think you might also be able to use a stopgap crate like konst
crate to do work around this until they stabilize something around this.
HTH