Immutable struct with mutable reference members

Viewed 64

Is my understanding correct that in Rust it is not possible to protect reference members of a struct from modification while having the reference target values mutable? (Without runtime borrow checking that is.) For example:

struct MyData<'a> {
    pub some_ref: &'a mut i32,
}

fn doit<'a>(data: &mut MyData<'a>, other_ref: &'a mut i32) {
    // I want to be able to do the following here:
    *data.some_ref = 22;
    // but make it impossible to do the following:
    data.some_ref = other_ref;
}

Not being able to change the reference value may be useful in certain FFI situations. FFI and the performance requirements reasons prevent the use of runtime borrow checking here.

In C++ it can be expressed like this:

struct MyData {
    int* const some_ref;
};

void doit(const MyData &data, int* other_ref) {
    // this is allowed:
    *data.some_ref = 22;
    // this is not:
    data.some_ref = other_ref; // compile error
}
2 Answers

You can create a wrapper type around the reference. If the constructor is private, and so is the wrapped reference field, you cannot replace the reference itself. You can then implement DerefMut to allow changing the referent.

pub struct ImmRef<'a> {
    inner: &'a mut i32,
}

impl<'a> ImmRef<'a> {
    fn new(inner: &'a mut i32) -> Self { Self { inner } }
}

impl std::ops::Deref for ImmRef<'_> {
    type Target = i32;
    fn deref(&self) -> &Self::Target { &*self.inner }
}
impl std::ops::DerefMut for ImmRef<'_> {
    fn deref_mut(&mut self) -> &mut Self::Target { &mut *self.inner }
}

struct MyData<'a> {
    pub some_ref: ImmRef<'a>,
}

fn doit<'a>(data: &mut MyData<'a>, other_ref: &'a mut i32) {
    // I want to be able to do the following here:
    *data.some_ref = 22;
    // but make it impossible to do the following:
    // data.some_ref = other_ref;
}

You can mark the newtype #[repr(transparent)] for FFI purposes.

But do note that if the code has some ImmRef<'a> available it can use tools such as std::mem::replace() to replace the reference.

Rust does not allow you to specify the mutability of individual fields like you can via const in C++. Instead, you should simply encapsulate the data by making it private and only allow modification through methods that you dictate:

struct MyData<'a> {
    some_ref: &'a mut i32,
}

impl MyData<'_> {
    pub fn set_ref(&mut self, other: i32) {
        *self.some_ref = other;
    }
}

That way, the field some_ref cannot be modified directly (outside of the module) and must use the available method.

Related