问题描述
我的C ++背景使我对内部可变性感到不舒服。 以下代码是我对此主题的调查。
我同意,从借阅检查员的角度来看, 每个内部结构可以引用的每个结构都有很多引用 迟早不可能更改;那显然是在哪里 内部的可变性可以提供帮助。
此外,在 The Rust Programming Language 的15.5 "RefCell and the Interior Mutability Pattern"章中,该示例
关于Messenger
特质及其在
MockMessenger
结构使我认为这是一个常见的API
设计甚至系统地更喜欢&self
胜过&mut self
如果很明显某种可变性是强制性的
早晚。
Messenger
的实现如何不改变其内部
发送消息时的状态?
唯一的例外是仅打印消息,这是一致的
&self
,但一般情况可能包含
写入某种内部流,这可能意味着缓冲,
正在更新错误标记...
所有这些当然都需要&mut self
,例如
impl Write for File
。
依靠内部可变性来解决此问题对我来说
例如在C ++中const_cast
个成员的mutable
滥用
因为我们在应用程序的其他地方并不一致
const
ness(C ++学习者的常见错误)。
因此,回到下面的示例代码中,我应该
- 使用
&mut self
(即使编译器没有抱怨,编译器也不会抱怨 (不是强制性的)从change_e()
到change_i()
,以便 保持与我更改 存储整数? - 继续使用
&self
,因为内部可变性允许它,甚至 我是否真的更改了存储的整数的值?
此决定不仅限于结构本身,而且会 对可以表达的内容有很大的影响 使用此结构的应用程序。 第二种解决方案肯定会有所帮助,因为仅 涉及共享引用,但与哪些内容一致 有望在Rust中使用。
我无法在以下位置找到该问题的答案 Rust API Guidelines。 是否还有其他Rust文档类似于 C++CoreGuidelines?
/*
$ rustc int_mut.rs && ./int_mut
initial: 1 2 3 4 5 6 7 8 9
change_a: 11 2 3 4 5 6 7 8 9
change_b: 11 22 3 4 5 6 7 8 9
change_c: 11 22 33 4 5 6 7 8 9
change_d: 11 22 33 44 5 6 7 8 9
change_e: 11 22 33 44 55 6 7 8 9
change_f: 11 22 33 44 55 66 7 8 9
change_g: 11 22 33 44 55 66 77 8 9
change_h: 11 22 33 44 55 66 77 88 9
change_i: 11 22 33 44 55 66 77 88 99
*/
struct Thing {
a: i32,b: std::Boxed::Box<i32>,c: std::rc::Rc<i32>,d: std::sync::Arc<i32>,e: std::sync::Mutex<i32>,f: std::sync::RwLock<i32>,g: std::cell::UnsafeCell<i32>,h: std::cell::Cell<i32>,i: std::cell::RefCell<i32>,}
impl Thing {
fn new() -> Self {
Self {
a: 1,b: std::Boxed::Box::new(2),c: std::rc::Rc::new(3),d: std::sync::Arc::new(4),e: std::sync::Mutex::new(5),f: std::sync::RwLock::new(6),g: std::cell::UnsafeCell::new(7),h: std::cell::Cell::new(8),i: std::cell::RefCell::new(9),}
}
fn show(&self) -> String // & is enough (read-only)
{
format!(
"{:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3}",self.a,self.b,self.c,self.d,self.e.lock().unwrap(),self.f.read().unwrap(),unsafe { *self.g.get() },self.h.get(),self.i.borrow(),)
}
fn change_a(&mut self) // &mut is mandatory
{
let target = &mut self.a;
*target += 10;
}
fn change_b(&mut self) // &mut is mandatory
{
let target = self.b.as_mut();
*target += 20;
}
fn change_c(&mut self) // &mut is mandatory
{
let target = std::rc::Rc::get_mut(&mut self.c).unwrap();
*target += 30;
}
fn change_d(&mut self) // &mut is mandatory
{
let target = std::sync::Arc::get_mut(&mut self.d).unwrap();
*target += 40;
}
fn change_e(&self) // !!! no &mut here !!!
{
// With C++,a std::mutex protecting a separate integer (e)
// would have been used as two data members of the structure.
// As our intent is to alter the integer (e),and because
// std::mutex::lock() is _NOT_ const (but it's an internal
// that Could have been hidden behind the mutable keyword),// this member function would _NOT_ be const in C++.
// But here,&self (equivalent of a const member function)
// is accepted although we actually change the internal
// state of the structure (the protected integer).
let mut target = self.e.lock().unwrap();
*target += 50;
}
fn change_f(&self) // !!! no &mut here !!!
{
// actually alters the integer (as with e)
let mut target = self.f.write().unwrap();
*target += 60;
}
fn change_g(&self) // !!! no &mut here !!!
{
// actually alters the integer (as with e,f)
let target = self.g.get();
unsafe { *target += 70 };
}
fn change_h(&self) // !!! no &mut here !!!
{
// actually alters the integer (as with e,f,g)
self.h.set(self.h.get() + 80);
}
fn change_i(&self) // !!! no &mut here !!!
{
// actually alters the integer (as with e,g,h)
let mut target = self.i.borrow_mut();
*target += 90;
}
}
fn main() {
let mut t = Thing::new();
println!(" initial: {}",t.show());
t.change_a();
println!("change_a: {}",t.show());
t.change_b();
println!("change_b: {}",t.show());
t.change_c();
println!("change_c: {}",t.show());
t.change_d();
println!("change_d: {}",t.show());
t.change_e();
println!("change_e: {}",t.show());
t.change_f();
println!("change_f: {}",t.show());
t.change_g();
println!("change_g: {}",t.show());
t.change_h();
println!("change_h: {}",t.show());
t.change_i();
println!("change_i: {}",t.show());
}
解决方法
依靠内部可变性来解决此问题对我来说 例如在C ++中
const_cast
个成员的mutable
滥用 因为我们在应用程序的其他地方并不一致const
ness(C ++学习者的常见错误)。
这是在C ++上下文中完全可以理解的思想。之所以不准确,是因为C ++和Rust具有不同的可变性概念。
在某种程度上,Rust的mut
关键字实际上具有两种含义。在模式中,它表示“可变”,在引用类型中,它表示“排他”。 &self
和&mut self
之间的区别实际上不是self
是否可以被突变,而是是否可以被别名。
在Messenger
示例中,首先,我们不要太在意它。它旨在说明语言功能,而不必说明系统设计。但是我们可以想象为什么会使用&self
:Messenger
是由共享的结构实现的,因此不同的代码段可以保存对同一对象的引用,并且使用它来send
警报而无需彼此协调。如果send
要使用&mut self
,则这将是无用的,因为一次只能存在一个&mut self
引用。无法将消息发送到共享的Messenger
(不通过Mutex
或其他方式添加内部可变性的外部层)。
另一方面,每个 C ++参考和指针都可以使用别名。¹因此,在Rust术语中,C ++中的 all 可变性是“内部”可变性! Rust没有与C ++中的mutable
等效,因为Rust没有const
成员(这里的口号是“可变性是绑定的属性,而不是类型”)。 Rust 确实与const_cast
等效,但仅用于原始指针,因为将共享的&
引用转换为专有的&mut
引用是不合理的。相反,C ++却没有Cell
或RefCell
之类的东西,因为每个值已经隐式地位于UnsafeCell
之后。
所以,回到我下面的示例代码中,我[...]
这实际上取决于Thing
的预期语义。 Thing
的本质是要共享的,例如通道终结点或文件?在共享(别名)引用上调用change_e
是否有意义?如果是这样,请使用内部可变性在&self
上公开一个方法。 Thing
主要是用于存储数据的容器吗?有时共享它并使其排他性是否有意义?然后Thing
可能不应该使用内部可变性,并让库的用户决定是否需要处理共享突变。
另请参见
- What's the difference between placing "mut" before a variable name and after the ":"?
- Why is the mutability of a variable not reflected in its type signature in Rust?
- Need holistic explanation about Rust's cell and reference counted types
¹实际上,C ++确实具有使指针工作类似于Rust中引用的功能。有点儿。 restrict
是C ++中的非标准扩展,但它是C99的一部分。 Rust的共享(&
)引用类似于const *restrict
指针,排他(&mut
)引用类似于非const
*restrict
指针。参见What does the restrict keyword mean in C++?
您上一次在C ++中故意使用restrict
(或__restrict
等)指针是什么时候?不要去考虑它。答案是“从不”。 restrict
比常规指针启用更积极的优化,但是很难正确使用它,因为您必须非常小心地使用别名,并且编译器不提供任何帮助。从根本上说,这是一个巨大的步枪,几乎没有人使用它。为了使值得使用restrict
的方式与在C ++中使用const
的方式一样,您需要能够在函数上注释哪些指针可以在其他时间别名为其他指针,有关何时可以有效使用指针的一些规则,并通过了编译器检查,以检查每个函数中是否遵循这些规则。就像某种...检查器。