6

It is possible to coerce &mut T into &T but it doesn't work if the type mismatch happens within a type constructor.

playground

use ndarray::*; // 0.13.0

fn print(a: &ArrayView1<i32>) {
    println!("{:?}", a);
}

pub fn test() {
    let mut x = array![1i32, 2, 3];
    print(&x.view_mut());
}

For the above code I get following error:

  |
9 |     print(&x.view_mut());
  |           ^^^^^^^^^^^^^ types differ in mutability
  |
  = note: expected reference `&ndarray::ArrayBase<ndarray::ViewRepr<&i32>, ndarray::dimension::dim::Dim<[usize; 1]>>`
             found reference `&ndarray::ArrayBase<ndarray::ViewRepr<&mut i32>, ndarray::dimension::dim::Dim<[usize; 1]>>`

It is safe to coerce &mut i32 to &i32 so why it is not applied in this situation? Could you provide some examples on how could it possibly backfire?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
pkubik
  • 780
  • 6
  • 19

3 Answers3

6

In general, it's not safe to coerce Type<&mut T> into Type<&T>.

For example, consider this wrapper type, which is implemented without any unsafe code and is therefore sound:

#[derive(Copy, Clone)]
struct Wrapper<T>(T);

impl<T: Deref> Deref for Wrapper<T> {
    type Target = T::Target;
    fn deref(&self) -> &T::Target { &self.0 }
}

impl<T: DerefMut> DerefMut for Wrapper<T> {
    fn deref_mut(&mut self) -> &mut T::Target { &mut self.0 }
}

This type has the property that &Wrapper<&T> automatically dereferences to &T, and &mut Wrapper<&mut T> automatically dereferences to &mut T. In addition, Wrapper<T> is copyable if T is.

Assume that there exists a function that can take a &Wrapper<&mut T> and coerce it into a &Wrapper<&T>:

fn downgrade_wrapper_ref<'a, 'b, T: ?Sized>(w: &'a Wrapper<&'b mut T>) -> &'a Wrapper<&'b T> {
    unsafe {
        // the internals of this function is not important
    }
}

By using this function, it is possible to get a mutable and immutable reference to the same value at the same time:

fn main() {
    let mut value: i32 = 0;

    let mut x: Wrapper<&mut i32> = Wrapper(&mut value);

    let x_ref: &Wrapper<&mut i32> = &x;
    let y_ref: &Wrapper<&i32> = downgrade_wrapper_ref(x_ref);
    let y: Wrapper<&i32> = *y_ref;

    let a: &mut i32 = &mut *x;
    let b: &i32 = &*y;

    // these two lines will print the same addresses
    // meaning the references point to the same value!
    println!("a = {:p}", a as &mut i32); // "a = 0x7ffe56ca6ba4"
    println!("b = {:p}", b as &i32);     // "b = 0x7ffe56ca6ba4"
}

Full playground example

This is not allowed in Rust, leads to undefined behavior and means that the function downgrade_wrapper_ref is unsound in this case. There may be other specific cases where you, as the programmer, can guarantee that this won't happen, but it still requires you to implement it specifically for those case, using unsafe code, to ensure that you take the responsibility of making those guarantees.

Frxstrem
  • 38,761
  • 9
  • 79
  • 119
  • Don't we end up in the same situation without the Wrapper in safe Rust? https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=eac012dcfda55dc48bd6b8a23843da54 – pkubik Apr 12 '20 at 22:38
  • @pkubik I guess that's a decent counter-example, my code should probably have been more explicit, but if you add type annotations to the `println!` statements, you'll get a lifetime error in your example but not in mine. Compare [my code](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5ac31603ff6d24e7809b0122ec4bd942) (updated, compiles) vs [your code](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4dd0bc74fbb8b6bdd5082108167146fa) (updated, does not compile). I'll update my answer to include this. – Frxstrem Apr 12 '20 at 22:55
  • (I guess the problem was that it's not being *used* as a mutable reference, so it doesn't actually check that it's actually mutable. Adding explicit type annotations would ensure that the mutable reference is being used mutably, which should cause a lifetime error but doesn't.) – Frxstrem Apr 12 '20 at 22:58
  • Yes, that's actually interesting that a definition of a reference variable is not treated as a borrow by the compiler and you must actually use it to cause an error. I guess it gives some more flexibility when organizing the code. – pkubik Apr 13 '20 at 13:13
3

Consider this check for an empty string that relies on content staying unchanged for the runtime of the is_empty function (for illustration purposes only, don't use this in production code):

struct Container<T> {
    content: T
}

impl<T> Container<T> {
    fn new(content: T) -> Self
    {
        Self { content }
    }
}

impl<'a> Container<&'a String> {
    fn is_empty(&self, s: &str) -> bool
    {
        let str = format!("{}{}", self.content, s);
        &str == s
    }
}

fn main() {
    let mut foo : String = "foo".to_owned();
    let container : Container<&mut String> = Container::new(&mut foo);

    std::thread::spawn(|| {
        container.content.replace_range(1..2, "");
    });

    println!("an empty str is actually empty: {}", container.is_empty(""))
}

(Playground)

This code does not compile since &mut String does not coerce into &String. If it did, however, it would be possible that the newly created thread changed the content after the format! call but before the equal comparison in the is_empty function, thereby invalidating the assumption that the container's content was immutable, which is required for the empty check.

msrd0
  • 7,816
  • 9
  • 47
  • 82
  • I think I got it. If I tried to do the same with the String directly I would get `cannot borrow foo as immutable because it is also borrowed as mutable`. On the other hand `Container` is always passed by immutable reference regardless of the `content` type so this mechanism wouldn't be triggered. – pkubik Apr 12 '20 at 22:04
0

It seems type coercions don't apply to array elements when array is the function parameter type.

playground

Kate W
  • 21
  • 1
  • 3
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/33177061) – possum Nov 20 '22 at 16:48