Search code examples
rustimmutabilitycoerciontype-constructor

Why Rust can't coerce mutable reference to immutable reference in a type constructor?


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?


Solution

  • 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.