How to prevent changing my smart pointer, but not the data it points to?

I have this custom type called a SchemaBox, which is similar to a Box<dyn Any> in purpose, but instead of storing the TypeId of the inner type, it stores it's Schema, which is a description of the type's memory layout.

pub struct SchemaBox {
    ptr: OwningPtr<'static>,
    schema: &'static Schema,

I have an issue where I need to give you something like a &mut SchemaBox, but I need to prevent you from replacing that schema box with a box with a different schema.

This is feeling vaguely familiar to Pin, but I haven't put my finger on whether or not I can use it to help.

Because fundamentally I need to allow you to use any functions on SchemaBox that modify data through the pointer, but never let you replace that box with a new box that has a different pointer or schema.

I can't have a Pin<SchemaBox> because SchemaBox doesn't implement Deref, since there's no concrete type to deref to. It's almost like I need a new Pin type that lets you call methods on the type, but not let you mem::swap() or even *schema_box_1 = schema_box_2.

Any ideas?

Oh, I think I may have just figured it out.

I already have a couple types, SchemaRef<'pointer> and SchemaRefMut<'pointer>, which are essentially a borrow to a SchemaBox or any other value that has a schema. If I make sure to give out only SchemaRefMut, then it becomes impossible to change the pointer of the box.

I'm going to try that out and see how it works.


In general in Rust, given a T or a &mut T, the owner can choose to replace the T with another of the same type.

Therefore, if whatever you do is both sound and achieves your goal, then it will be an example of interior mutability — some mechanism whereby the caller can start with &Container and end up with &mut Contents, like RefCell. If the operation doesn't start with &, then you will likely not have succeeded in preventing the mutation you want to prevent.


Good point.

While exploring this with Box<dyn Any>, which is similar to what I'm trying to accomplish, I just stumbled on an interesting trick that might give me what I want, though. Trait objects which are not Sized ( that might be all of them IIUC ), cannot be swapped or re-assigned. So this doesn't work:

fn main() {
    let mut data = Box::new(0usize) as Box<dyn Any>;
    let mut data2 = Box::new(String::from("hi")) as Box<dyn Any>;
    let data_ref = &mut data as &mut dyn Any;
    let data2_ref = &mut data2 as &mut dyn Any;

    std::mem::swap(data_ref, data2_ref)
   Compiling playground v0.0.1 (/playground)
error[E0277]: the size for values of type `(dyn Any + 'static)` cannot be known at compilation time
 --> src/
9 |     std::mem::swap(data_ref, data2_ref)
  |     ^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  = help: the trait `Sized` is not implemented for `(dyn Any + 'static)`
note: required by a bound in `std::mem::swap`
 --> /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/core/src/mem/

The same happens with trying to assign to it:

error[E0277]: the size for values of type `(dyn Any + 'static)` cannot be known at compilation time
 --> src/
9 |     *data_ref = *data2_ref;
  |     ^^^^^^^^^ doesn't have a size known at compile-time
  = help: the trait `Sized` is not implemented for `(dyn Any + 'static)`
  = note: the left-hand-side of an assignment must have a statically known size

I'm wondering if I could just return a trait object like &mut dyn SchemaRefMutApi to grant access to the methods on SchemaRefMut, but not allow swapping or assignment to the actual value.

I'm assuming this introduces runtime overhead, though, even if I always know that the &mut dyn SchemaRefMutApi will concretely be a SchemaRefMut.

Is it possible maybe to create a type that is intentionally unsized, just to prevent swapping and assignment, without using a trait object?



Wait, I just realized, I'm pretty sure I was right earlier and returning a SchemaRefMut is fine, because it's like returning a &mut T that you got from a Box<T>.

Having a mutable reference allows you to change the data that is pointed to, but you can't modify the original box which is what I'm worried about. Nothing you do without having a &mut Box<T> will allow you to change the pointer that is inside of the orignal box. It will always point to the same memory, even if it has a method that returns a "copy" of it's internal pointer in the form of a &mut T.

For example:

use std::ops::DerefMut;

fn main() {
    let mut data = Box::new(0usize);
    let mut data_ref = data.deref_mut();

    let other_data_ref = &mut 7usize;

    data_ref = other_data_ref;

    assert_eq!(*data, 0);

My SchemaBox has a method as_mut():

    pub fn as_mut(&mut self) -> SchemaRefMut<'_, '_> {
        SchemaRefMut {
            ptr: self.ptr.as_mut(),
            schema: self.schema,
            parent_lifetime: PhantomData,

It returns a new SchemaRefMut that has a pointer in it with a lifetime tied to the SchemaBox. If I give you this new SchemaRefMut, then even if you replace that SchemaRefMut with a new SchemaRefMut that has a pointer to a new type, the pointer in the SchemaBox remains unchanged.

( ignore the double-lifetime thing, it should be un-related to this discussion )

That's cool even if I don't use it. Thanks. :smiley:

Yeah. A &mut T doesn't allow you to change the address of the pointed object. (As far as I can tell, anyway.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.