4

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
}
6
  • Do you change the reference only through FFI? What about initialization? Commented Aug 28, 2022 at 0:36
  • Imagine a program where the main part of it allocates the MyData struct and then loads two dynamic libraries. It gives these 2 dynamic libs pointers to the allocated MyData and allows the libs to access and change the value pointed at by the some_ref field (it takes care of ensuring they don't do it at the same time), but it must prevent them from changing the some_ref reference itself, because for its own reasons it must ensure that the data is located at that specific address where it allocated it. I bet one can come up with an example that does not involve dylibs too. Commented Aug 28, 2022 at 0:54
  • If you want protection against intentional misuse, this is not something Rust can provide (think unsafe code, rustc bugs, /proc/mem and friends). If you just want to protect again accidental misuse, this is another story. Commented Aug 28, 2022 at 1:06
  • All misuse. Accidental included. Commented Aug 28, 2022 at 1:16
  • Then your C++ code isn't correct. Code can cast the const away and assign to the reference. Commented Aug 28, 2022 at 1:17

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Yes, this is the solution I was looking for. The only potential problem here is that if somehow somewhere you got an individual ImmRef value--you can still assign it into the MyData::some_ref field. However, this is not a problem if you only see ImmRefs as members of other structs that you don't own (so you can't move it out from another struct and you don't implement Copy for it). Thanks!
@LevHimmelfarb If you have two mutable instances of MyData you can swap their references.
0

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.

1 Comment

This could be a solution, although rather bulky. The solution with creating a wrapper implementation of DerefMut and hiding the ways to construct a wrapper instance from the client code works better in this particular case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.