为什么 Rust 不能在类型构造函数中将可变引用强制转换为不可变引用?
Why Rust can't coerce mutable reference to immutable reference in a type constructor?
可以将 &mut T
强制转换为 &T
,但如果类型构造函数中发生类型不匹配,则此方法无效。
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());
}
对于上面的代码,我得到以下错误:
|
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]>>`
将 &mut i32
强制转换为 &i32
是安全的,那么为什么不在这种情况下应用呢?你能举一些例子说明它怎么会适得其反吗?
考虑对依赖于 content
在 is_empty
函数运行时保持不变的空字符串进行此检查(仅用于说明目的,请勿在生产代码中使用它):
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(""))
}
此代码无法编译,因为 &mut String
不会强制转换为 &String
。但是,如果它这样做了,那么新创建的线程可能会在 format!
调用之后但在 is_empty
函数中进行相等比较之前更改 content
,从而使假设无效容器的内容是不可变的,这是空检查所必需的。
一般而言,将 Type<&mut T>
强制转换为 Type<&T>
是不安全的。
例如,考虑这种包装器类型,它的实现没有任何不安全代码,因此是合理的:
#[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 }
}
此类型具有 &Wrapper<&T>
自动取消引用 &T
的 属性,&mut Wrapper<&mut T>
自动取消引用 &mut T
。此外,Wrapper<T>
是可复制的,如果 T
是。
假设存在一个函数可以将 &Wrapper<&mut T>
转换为 &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
}
}
通过使用这个函数,可以同时获取对同一个值的可变引用和不可变引用:
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"
}
这在 Rust 中是不允许的,会导致未定义的行为,并且意味着函数 downgrade_wrapper_ref
在这种情况下是不合理的。可能还有其他特定情况,作为程序员,您可以保证不会发生这种情况,但仍然需要您针对这些情况专门实现它,使用 unsafe
代码,以确保您承担责任做出这些保证。
可以将 &mut T
强制转换为 &T
,但如果类型构造函数中发生类型不匹配,则此方法无效。
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());
}
对于上面的代码,我得到以下错误:
|
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]>>`
将 &mut i32
强制转换为 &i32
是安全的,那么为什么不在这种情况下应用呢?你能举一些例子说明它怎么会适得其反吗?
考虑对依赖于 content
在 is_empty
函数运行时保持不变的空字符串进行此检查(仅用于说明目的,请勿在生产代码中使用它):
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(""))
}
此代码无法编译,因为 &mut String
不会强制转换为 &String
。但是,如果它这样做了,那么新创建的线程可能会在 format!
调用之后但在 is_empty
函数中进行相等比较之前更改 content
,从而使假设无效容器的内容是不可变的,这是空检查所必需的。
一般而言,将 Type<&mut T>
强制转换为 Type<&T>
是不安全的。
例如,考虑这种包装器类型,它的实现没有任何不安全代码,因此是合理的:
#[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 }
}
此类型具有 &Wrapper<&T>
自动取消引用 &T
的 属性,&mut Wrapper<&mut T>
自动取消引用 &mut T
。此外,Wrapper<T>
是可复制的,如果 T
是。
假设存在一个函数可以将 &Wrapper<&mut T>
转换为 &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
}
}
通过使用这个函数,可以同时获取对同一个值的可变引用和不可变引用:
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"
}
这在 Rust 中是不允许的,会导致未定义的行为,并且意味着函数 downgrade_wrapper_ref
在这种情况下是不合理的。可能还有其他特定情况,作为程序员,您可以保证不会发生这种情况,但仍然需要您针对这些情况专门实现它,使用 unsafe
代码,以确保您承担责任做出这些保证。