【发布时间】:2020-12-08 17:52:19
【问题描述】:
我在 C++ 方面的背景让我对内部可变性 感到不舒服。 下面的代码是我围绕这个主题的调查。
我同意,从借用检查器的角度来看,处理 每个内部状态可以的单个结构上的许多引用 迟早要改变是不可能的;这显然是在哪里 内部可变性可以提供帮助。
此外,在 The Rust Programming Language 的 15.5 "RefCell and the Interior Mutability Pattern" 章节中,示例
关于 Messenger 特征及其在
MockMessenger struct 让我觉得它是一个通用的 API
设计系统地更喜欢&self而不是&mut self
如果很明显某种可变性将是强制性的
迟早。
Messenger 的实现如何不改变其内部
发送消息时的状态?
例外只是打印消息,这是一致的
&self,但一般情况可能包括
写入某种内部流,这可能意味着缓冲,
更新错误标志...
所有这些当然需要&mut self,例如
impl Write for File.
在我看来,依靠内部可变性来解决这个问题
就像,在 C++ 中,const_casting 或滥用 mutable 成员只是
因为在应用程序的其他地方我们并不一致
constness(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, f, 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());
}
【问题讨论】:
-
内部可变性只有在你不能这样做的情况下才应该使用,例如互斥锁使用它,因为没有它就无法工作。在应用程序代码中很少使用它,出于显而易见的原因,人们应该尽量避免它。
-
@Stargateur 那么,我是否应该认为本书中的
Messengertrait 示例具有误导性?设计这样的特征意味着强制实现依赖于内部可变性。 -
没有信使特征是“我们有一个不应该需要改变状态的特征”但用户想要,所以用户的解决方案是具有内部可变性,比如在示例中以跟踪过去的消息。
-
请注意,虽然
Writetrait 确实使用了&mut self,但File本身实际上没有。您可以使用implementation for&'_ File写入和读取&File。 (这不涉及内部可变性;这只是底层 OS API 的工作方式。)
标签: rust api-design interior-mutability