Safe `&&T` to `&&mut T` conversion

Hello, for some generic programming I need to convert &&T to &&mut T. Is it possible in safe Rust? If not, would it be hard to add into core?

Short answer: no.
In fact, doing this, even in unsafe Rust, is guaranteed to cause immediately undefined behavior.

Also, having a &mut T means this is the only pointer to T, and so it doesn't really make much sense to have an immutable reference to a mutable one, even if it is possible.

What you're trying to achieve can be done with interior mutability, so I suggest you to look to RefCell or Mutex.

2 Likes

Miri is fine with it (so this is not undefined behavior):

cargo miri run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `/home/ddystopia/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner /home/ddystopia/code/play/target/miri/x86_64-unknown-linux-gnu/debug/play`
0
fn main() {
    let foo = 0;
    let foo_ref = &foo;
    let foo_ref_ref = convert(&foo_ref);
    println!("{}", foo_ref_ref);
}

fn convert<'a, 'b>(foo: &'a &'b i32) -> &'a &'b mut i32 {
    let ptr = foo as *const &'b i32;
    let ptr = ptr as *const &'b mut i32;
    unsafe { &*ptr }
}

How would you use this &&mut T now? Because it is behind a shared reference, you can't modify it. So knowing why you need specifically a &&mut T will help solve the problem.

1 Like

Disregarding the question of whether this is UB or not, I'd like to clarify that that's not true in general. Miri does not have false positives (modulo bugs), but it may have false negatives. That is, it doesn't catch all UB.

8 Likes

Please, don't be so agitated, this is supposed to be a friendly user's forum. I have my uses, I told: generic programming. It doesn't change the question in any way.

Yes, I agree, but in this case you may even spell the logic manually ( I didn't do this because @CieriA obviously won't agree ) : no &mut T is being created at all, thus no aliasing violation is there.

I asked that just because it may have helped solve the problem. Anyway, I don't think a safe way of casting &&T to &&mut T exists.

(Also, I wasn't trying to be rude at all lol)

1 Like

why do you need &&mut T in the first place?

even with &&mut T, you can only call methods on T that take &self (NOT &mut self) as receiver, which you can already do with &&T. so please provide more details of the problem to justify the need for &&mut T.

8 Likes

You might be interested in this prior discussion: Is transmuting `&’a &’b T` to `&’a &’b mut T` sound or UB? · Issue #270 · rust-lang/unsafe-code-guidelines · GitHub

Funnily enough the opposite conversion (&&mut T to &&T) is actually unsound because it allows to extend the borrow (with &'short &'long mut T you effectively have shared access to T in 'short, but with &'short &'long T you have shared access to T in 'long!)

8 Likes

Interesting, why &'short &'long T would give shared access to T in 'long?

When implementing stuff for &mut T sometimes trait may require &Self too (fn (&mut self, other: &Self)) and other code that wan't to reuse that implementation would need &mut &mut T for the &mut self and &&mut T for other. Like, generics sometimes compose in a strange ways.

Of course it does.

You are doing something that Rustonomicon explains like this:

  • Transmuting an & to &mut is always Undefined Behavior.
  • No you can't do it.
  • No you're not special.

That's the strongest kind of “no” that have seen in that book.

Now, it's true that Rustonomicon is not 100% right: in some very convoluted and rare corner case this could be safe. It may never be sound, mind you… but it can be safe.

And then you come and declare that you want to play with sparklers in the gunpowder warehouse and ask why they aren’t there by the entrance…

You have just told us that you plan to do that.

Yes. And compiler doesn't like that.

This function couldn't access second argument in any shape at all. Why pass Self there?

I believe this is hardcoded in the reborrowing logic, but another reason is that you can dereference it to get a &'long T, just like you can dereference any &'short U to get a U if U is copy, and &'long T is Copy.

4 Likes

so what's the problem?

in order to call fn (&mut self, other: &Self), you need a Self first to create a &Self, or reborrow from an existing &Self. it's no different when Self is &mut T: in order to create a &&mut T for other, you need a &mut T first, or reborrow from an existing &&mut T.

if you only have &&T, but you need a &&mut T, it's like you need other: &Self but you have some &NotSelf value. you simply don't have the arguments to call the function.

as I already said, there's nothing much you can do with &&mut T that you can't already do with &&T. either you have some arcane use case that I failed to grasp, or you misunderstood the problem yourself.

1 Like

To be honest I just don't think it is that big of a problem to explain the whole thing with examples. Here is one relatively close enough

trait Foo {
  fn foo(&mut self, other: &Self);
}

// Note: circumstances prevent implementing directly on T or changing Foo
impl<T> Foo for &mut T {
  fn foo(&mut self, other: &Self) { todo!("Mutate self based on other") }
}

impl<T> Foo for Box<T> {
  fn foo(&mut self, other: &Self) {
      // No way to reuse previous impl block like that
      let this: &mut T = &mut **self;
      let other: &&mut T = /* what? */;
      this.foo(other);
  }
}



That blank can't be filled in, since no method on Box<T> or &Box<T> accepts a &Box<T> and returns an &mut T. Implementing such a method would run headlong into the caveat khimru quoted: it's undefined behaviour to do so in the general case, and nothing about Box provides any safety from that.

Your trait design paints you into this corner, and I would suggest redesigning Foo to be implemented on T rather than on &mut T:

trait Foo {
  fn foo(&mut self, other: &Self);
}

impl<T> Foo for T {
  fn foo(&mut self /* &mut T */, other: &Self /* &T */) { todo!(); }
}

Of course, the range of possible implementations for Foo over T is very limited (possibly even empty), but that can be improved by constraining the impl to types T that satisfy some other impl you can use to implement foo().

Alternately, if you really need foo to accept a &mut &mut T as its left input, I might change Foo as follows:

trait Foo {
  type From;
  fn foo(&mut self, other: &From);
}

impl<T> Foo for &mut T {
  type From = T;
  fn Foo(&mut self, other: &T) { todo!(); }
}

at that point you no longer need to convert from Box<T> to &mut T.

You've commented that you can't change Foo, which is nonsense on its face (it's just code; even if it's from a foreign crate, you can copy the crate into your workspace and modify it), but to allow for that anyways, you could either implement Foo for Box<T> without relying on the &mut T impl, or opt not to use Foo and to design your system around not having whatever functions depend on Foo.

The only general, sound ways to obtain a &mut T are either from a mut T, or by reborrowing or moving an existing &mut T, or by using a container like RefCell or Mutex that can handle internal mutability.

1 Like

In your code
fn foo(&mut self, other: &Self)

other is &T

Why are you trying to convert &T to &&mut T just to give it to function needing &T?

If you only want to read data from it, but don't want to modify it?

todo!("Mutate self based on other")

Converting read reference to write reference is illogical, because here can only be one reference if here are write reference, but here can be many read references.

And from function signature we can't say how many read references here are of same data in this application, so converting one read reference to write reference is illogical and memory unsafe.

It's unsafe in the Rust terms (and thus function that does that would need to be marked unsafe), but not necessarily incorrect. Note that it's only UB to have two such converted objects, if you have just one then it's allowed… just not clear that the need is so common that we need special function for that.

It's too easy to abuse.

Just to add more info to this answer, this is also undefined behavior in C++.

While in C++ we have the specific const_cast, if you attempt to modify an object that was originally const through a non-const pointer obtained via const_cast , it results in undefined behavior.

3 Likes