Rust: [tracking issue] dereferencing raw pointers inside constants (const_raw_ptr_deref)

Created on 29 Jun 2018  路  9Comments  路  Source: rust-lang/rust

The const_raw_ptr_deref feature allows dereferencing raw pointers inside constants.

This can already be emulated via union transmuting raw pointers to references and referencing the references. This feature gate helps us experiment with the semantics of actually dereferencing normally.

Blocked on https://github.com/rust-lang/const-eval/issues/14.

A-const-eval A-const-fn B-unstable C-tracking-issue T-lang

Most helpful comment

FWIW, the use case I have for this is to create #[repr(transparent)] DSTs as consts:

#[repr(transparent)]
pub struct CStr(str);

impl CStr {
    /// Creates a new CStr from a str without performing any additional checks. `data` _must_ end
    /// with a NUL byte, and should only have only a single NUL byte, or the string will be
    /// truncated.
    pub const unsafe fn new_unchecked(data: &str) -> &CStr {
        &*(data as *const str as *const CStr)
    }
}

All 9 comments

What does this feature actually do?

To my mind the primary use case for this is going to be static references to memory-mapped I/O registers in embedded development; however, the obvious thing

static REG_DEBUG_ENABLE: &'static u16 = unsafe { &*(0x4fff780 as *mut u16) };

gets me the error

this static likely exhibits undefined behavior

type validation failed: encountered integer pointer in non-ZST reference

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

which suggests that it's constructing 0x4fff780 as *mut u16 on the host machine at compile-time, dereferencing it and substituting what it found so that dereferencing REG_DEBUG_ENABLE on the target machine at runtime produces the same value, when I just wanted a typecast.

which suggests that it's constructing 0x4fff780 as *mut u16 on the host machine at compile-time,

Not quite. That happens transparently. The error comes from the sanity checks on the value of constants and statics.

This feature is for when you converted a safe reference to a raw pointer, allowing you to turn it back into a safe reference.

What you are trying to do is simply not allowed (yet?).

Side-note: Additionally it seems dangerous to create references to registers without using volatile accessors. A &'static u16 guarantees (to best of my knowledge) that the value pointed at will not change at all during the entire runtime of your program.

Yes, &'static u16 is wrong for MMIO, you'd want something like &'static VolatileCell<u16> (for a suitable definition of VolatileCell), though that doesn't change anything with respect to the pointer casting that needs to happen.

I wasn't being very careful when throwing out an example. Pretend I used &mut instead and waved my hands about volatile accesses.

(Sidenote: I'm surprised VolatileCell isn't provided in the standard library.)

&'static mut T is definitely UB here.

And once you are at the VolatileCell level, you might as well make that a ZST with a phantomdata on your concrete type and everything will work out nicely.

Okay, now I'm lost. It sounds like you just told me it's undefined behaviour to access system memory unless I allocate something on the stack first.

If you had a way to create &'static mut T from an integer safely, then you could do that twice and thus end up with two &mut T to the same address. This is UB in Rust.

What I was suggesting was that you'd use

#![feature(const_raw_ptr_deref)]

const FOO: &'static VolatileCell<u32> = unsafe { &*(0x4fff780 as *const VolatileCell<u32>) };
struct VolatileCell<T: Copy> {
    marker: std::marker::PhantomData<T>,
}
impl<T: Copy> VolatileCell<T> {
    fn read(&self) -> T {
        unsafe {
            std::ptr::read_volatile(self as *const _ as *const T)
        }
    }
}

to manage the access to device addresses

FWIW, the use case I have for this is to create #[repr(transparent)] DSTs as consts:

#[repr(transparent)]
pub struct CStr(str);

impl CStr {
    /// Creates a new CStr from a str without performing any additional checks. `data` _must_ end
    /// with a NUL byte, and should only have only a single NUL byte, or the string will be
    /// truncated.
    pub const unsafe fn new_unchecked(data: &str) -> &CStr {
        &*(data as *const str as *const CStr)
    }
}

Related: every function in std::ptr should be const. AFAICT there's no reason for them not to be.

Was this page helpful?
0 / 5 - 0 ratings