I have this C code which declare some configuration for an embedded radio chip: extract.h · GitHub
I have a wrapper around the C library that manage the chip's radio and want to pass this construct to a configuration function (already bindgenerated).
This is where I am right now, but I can't make more progress:
Giving me this error:
error[E0080]: it is undefined behavior to use this value
--> src\main.rs:39:1
|
39 | / const PHY_INFO: [u32; 10] = [
40 | | 3,
41 | | 0x000F0F0F,
42 | | 0,
... |
54 | | (1 << 8) | 1,
55 | | ];
| |__^ type validation failed: encountered a pointer, but expected plain (non-pointer) bytes
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0277]: `*const u32` cannot be shared between threads safely
--> src\main.rs:63:1
|
63 | / static CHANNEL_CFG_ENTRY: RAIL_ChannelConfigEntry_t = RAIL_ChannelConfigEntry_t {
64 | | phyConfigDeltaAdd: core::ptr::null(),
65 | | baseFrequency: 915000000,
66 | | channelSpacing: 1000000,
... |
71 | | attr: &CHANNEL_CFG_ENTRY_ATTR,
72 | | };
| |__^ `*const u32` cannot be shared between threads safely
|
= help: within `rail_sys::RAIL_ChannelConfigEntry`, the trait `core::marker::Sync` is not implemented for `*const u32`
= note: required because it appears within the type `rail_sys::RAIL_ChannelConfigEntry`
= note: shared static variables must have a type that implements `Sync`
error[E0277]: `*const rail_sys::RAIL_ChannelConfigEntryAttr` cannot be shared between threads safely
--> src\main.rs:63:1
|
63 | / static CHANNEL_CFG_ENTRY: RAIL_ChannelConfigEntry_t = RAIL_ChannelConfigEntry_t {
64 | | phyConfigDeltaAdd: core::ptr::null(),
65 | | baseFrequency: 915000000,
66 | | channelSpacing: 1000000,
... |
71 | | attr: &CHANNEL_CFG_ENTRY_ATTR,
72 | | };
| |__^ `*const rail_sys::RAIL_ChannelConfigEntryAttr` cannot be shared between threads safely
|
= help: within `rail_sys::RAIL_ChannelConfigEntry`, the trait `core::marker::Sync` is not implemented for `*const rail_sys::RAIL_ChannelConfigEntryAttr`
= note: required because it appears within the type `rail_sys::RAIL_ChannelConfigEntry`
= note: shared static variables must have a type that implements `Sync`
The thing is that I want this code to be in the binary, which will be on the Flash memory of the chip, so it will not change anyway. It's just some configuration (weird) constructs that I'm trying to mimic in Rust.
Global variables in Rust are accessible from all threads (single-threaded programs don't exist according to Rust's memory model), so Rust demands that their content is thread-safe (called Sync)
Rust doesn't know what C does regarding thread safety, so pessimistically assumes that all raw (C) pointers are unsafe, and their access is not thread-safe, so structs containing them are not thread-safe either.
You have a few options:
If your types can be accessed from multiple threads at the same time, without extra precautions or synchronization, tell Rust so by adding unsafe impl Sync for YourType {}
If your types are not thread-safe, wrap them in a Mutex or use thread-local storage for them
Replace *const Foo with &'static Foo which is non-null pointer to immutable memory. It's binary-compatible with C pointer, but tells Rust that it's thread-safe.
While you were typing, I tried option 1. but it gives me:
error[E0080]: it is undefined behavior to use this value
--> src\main.rs:39:1
|
39 | / static PHY_INFO: [u32; 10] = [
40 | | 3,
41 | | 0x000F0F0F,
42 | | 0,
... |
54 | | (1 << 8) | 1,
55 | | ];
| |__^ type validation failed: encountered a pointer, but expected plain (non-pointer) bytes
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
I've seen that yes, this is why I have the Transmuter, found on this tracking issue. So… there's no solution then? I have to keep this config struct in C and use FFI - The Rustonomicon ?
Maybe?
The doc of the function that will use this construction says:
* @param[in] config A pointer to the channel configuration for your device.
* This pointer will be cached in the library so it must
* exist for the runtime of the application. Typically, this should be
* what is stored in Flash by the configuration tool.
So if the address of the struct does not move, it's ok to init it in main?
If you borrow something in main(), it'll be close enough (the borrow will last until end of main, and while something is borrowed, it can't be moved). And static mut is still an option.
For a quick follow up, it didn't work, I think the C function expect a pointer to a Flash location only. I tried to put the whole Rust struct in RAM (putting mut on all of them), but it was still crashing.
I ended up putting the config in a .c file.
So, I think this is actually the key bit, isn't it? You got this, from what I understood, even with static. (And you should indeed use static.)
So, this is basically the converse of this issue. But I don't think the answer is to relax the compiler checks. You see, you gave the compiler a type: [u32; 10]. But you lied! There's not 10 integers in there. One of the elements is a pointer. And no, pointers are not integers, they are something very different. (If you don't like that, I'm afraid neither Rust nor C are the language for you. Making pointers complicated is the only known way to combine low-level memory access and high-level optimizations in a single language. Something has to give. C pretends pointers are simple, but the cracks are already showing.)
So while for the converse issue I mentioned, one can argue that our check is overly aggressive -- the same does not work here. If we allow pointers disguised as integers in static or const, things will break badly.
So, what can you do? Well, stop lying to the compiler! Tell them about your true types. You have an array of things where each is an integer or a pointer. There's a way to say that:
#[repr(C)] // this is important to get C-like union layout
union PtrOrInt {
int: u32,
ptr: &'static Something
}
static PHY_INFO: [PtrOrInt; 10] = {
PtrOrInt { int: 3 },
...
PtrOrInt { ptr: &something },
...
};
I think with something like that you should be able to produce a result that matches C.
(I have defined a convenience macros to generate an array where for each element it automagically picks between a usize or a & _ reference according to whether the expression has a leading &).
I have taken the freedom to use usizes instead of u32s for places where addresses where being used, as that's the portable way to do it (in a 64-bit architecture a &thing address may not fit in a u32, resulting in the address being truncated, which would lead to many problems).
This won't help. The OP's problem was about stuff getting put into mutable memory, and statics with interior mutability (UnsafeCell) are just as mutable as static mut and treated the same by codegen and the linker.
Yes, I am well aware that FFI-wise there is no difference between the two, but since a cdylib Rust library is still a Rust library, using a static ...: Mut<_> is safer Rust-wise than static mut.
Providing this example in a pure FFI scenario helps prove that there should be nothing blocking the deprecation of static mut.