如何通过引用使用包含可变引用的不可变选项?
How can you use an immutable Option by reference that contains a mutable reference?
这是 Thing
:
struct Thing(i32);
impl Thing {
pub fn increment_self(&mut self) {
self.0 += 1;
println!("incremented: {}", self.0);
}
}
这里有一个函数试图改变 Thing
和 returns 是真还是假,这取决于 Thing
是否可用:
fn try_increment(handle: Option<&mut Thing>) -> bool {
if let Some(t) = handle {
t.increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
这是一个用法示例:
fn main() {
try_increment(None);
let mut thing = Thing(0);
try_increment(Some(&mut thing));
try_increment(Some(&mut thing));
try_increment(None);
}
如上所述,it works just fine (link to Rust playground)。输出如下:
warning: increment failed
incremented: 1
incremented: 2
warning: increment failed
当我想编写一个将 Thing
变异两次的函数时,问题就出现了。例如,以下内容不起作用:
fn try_increment_twice(handle: Option<&mut Thing>) {
try_increment(handle);
try_increment(handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
这个错误很有道理。第一次调用 try_increment(handle)
放弃了 handle
的所有权,因此第二次调用是非法的。通常情况下,Rust 编译器会产生一条合理的错误消息:
|
24 | try_increment(handle);
| ------ value moved here
25 | try_increment(handle);
| ^^^^^^ value used here after move
|
为了解决这个问题,我认为通过引用传递 handle
是有意义的。请注意,它应该是一个 immutable 引用,因为我不希望 try_increment
能够更改 handle
本身(将 None
分配给它,例如)只能对其值调用突变。
我的问题是我不知道该怎么做。
这里is the closest working version that I could get:
struct Thing(i32);
impl Thing {
pub fn increment_self(&mut self) {
self.0 += 1;
println!("incremented: {}", self.0);
}
}
fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
// PROBLEM: this line is allowed!
// (*handle) = None;
if let Some(ref mut t) = handle {
t.increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(mut handle: Option<&mut Thing>) {
try_increment(&mut handle);
try_increment(&mut handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
此代码按预期运行,但 Option
现在由 mutable 引用传递,即 not我想要什么:
- 我可以通过将
None
重新分配给它来改变 Option
,打破所有后续突变。 (例如,取消注释第 12 行 ((*handle) = None;
)。)
- 太乱了:有一大堆无关的
&mut
到处都是。
- 真是一团糟:天知道为什么我必须在
if let
语句中使用 ref mut
而惯例是在其他地方使用 &mut
。
- 它违背了在编译器中使用复杂的借用检查和可变性检查规则的目的。
有没有什么方法可以真正实现我想要的:通过引用传递一个不可变的 Option
,并实际能够使用它的内容?
您不能从不可变引用中提取可变引用,即使是对其内部结构的引用。这就是重点!不可变引用的多个别名是允许的,因此,如果 Rust 允许您这样做,您可能会遇到两段代码能够同时改变相同数据的情况。
Rust 为 interior mutability 提供了几个逃生口,例如 RefCell
:
use std::cell::RefCell;
fn try_increment(handle: &Option<RefCell<Thing>>) -> bool {
if let Some(t) = handle {
t.borrow_mut().increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(handle: Option<RefCell<Thing>>) {
try_increment(&handle);
try_increment(&handle);
}
fn main() {
let mut thing = RefCell::new(Thing(0));
try_increment_twice(Some(thing));
try_increment_twice(None);
}
TL;DR:答案是否,我不能。
经过与@Peter Hall 和@Stargateur 的讨论,我明白了为什么我需要到处使用 &mut Option<&mut Thing>
。 RefCell<>
也是一个可行的解决方法,但它并不简洁,也没有真正实现我最初寻求实现的模式。
问题是这样的:如果一个人被允许改变一个只有 不可变 引用的对象 Option<&mut T>
一个人可以使用这个权力来破坏完全借用规则。具体来说,本质上,您可以对同一对象有许多 mutable 引用,因为您可以有许多这样的不可变引用。
我知道只有一个可变引用到Thing
(由Option<>
拥有)但是,一旦我开始引用 Option<>
,编译器就不再知道这些引用并不多了。
最佳版型如下:
fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
if let Some(ref mut t) = handle {
t.increment_self();
true
}
else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(mut handle: Option<&mut Thing>) {
try_increment(&mut handle);
try_increment(&mut handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
备注:
Option<>
持有对 Thing
的唯一现存可变引用
try_increment_twice()
获得 Option<>
的所有权
try_increment()
必须 将 Option<>
作为 &mut
以便编译器知道它具有对 [= 的唯一可变引用17=], 通话中
- 如果编译器知道
try_increment()
具有对 Option<>
的唯一可变引用,而 Option<>
包含对 Thing
的唯一可变引用,则编译器知道借用规则没有被侵犯了。
另一个实验
Option<>
的可变性问题仍然存在,因为可以调用 take()
等。在一个可变的 Option<>
上,打破了后面的一切。
为了实现我想要的模式,我需要 类似 和 Option<>
的东西,但是,即使它是 mutable,它不能被改变。像这样:
struct Handle<'a> {
value: Option<&'a mut Thing>,
}
impl<'a> Handle<'a> {
fn new(value: &'a mut Thing) -> Self {
Self {
value: Some(value),
}
}
fn empty() -> Self {
Self {
value: None,
}
}
fn try_mutate<T, F: Fn(&mut Thing) -> T>(&mut self, mutation: F) -> Option<T> {
if let Some(ref mut v) = self.value {
Some(mutation(v))
}
else {
None
}
}
}
现在,我想,我可以整天传递 &mut Handle
并且知道拥有 Handle
的人只能改变其内容,而不是句柄本身。 (See Playground)
不幸的是,即使这样也没有任何好处,因为如果你有一个可变引用,你总是可以用取消引用运算符重新分配它:
fn try_increment(handle: &mut Handle) -> bool {
if let Some(_) = handle.try_mutate(|t| { t.increment_self() }) {
// This breaks future calls:
(*handle) = Handle::empty();
true
}
else {
println!("warning: increment failed");
false
}
}
一切都很好。
底线结论:只需使用&mut Option<&mut T>
这是 Thing
:
struct Thing(i32);
impl Thing {
pub fn increment_self(&mut self) {
self.0 += 1;
println!("incremented: {}", self.0);
}
}
这里有一个函数试图改变 Thing
和 returns 是真还是假,这取决于 Thing
是否可用:
fn try_increment(handle: Option<&mut Thing>) -> bool {
if let Some(t) = handle {
t.increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
这是一个用法示例:
fn main() {
try_increment(None);
let mut thing = Thing(0);
try_increment(Some(&mut thing));
try_increment(Some(&mut thing));
try_increment(None);
}
如上所述,it works just fine (link to Rust playground)。输出如下:
warning: increment failed
incremented: 1
incremented: 2
warning: increment failed
当我想编写一个将 Thing
变异两次的函数时,问题就出现了。例如,以下内容不起作用:
fn try_increment_twice(handle: Option<&mut Thing>) {
try_increment(handle);
try_increment(handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
这个错误很有道理。第一次调用 try_increment(handle)
放弃了 handle
的所有权,因此第二次调用是非法的。通常情况下,Rust 编译器会产生一条合理的错误消息:
|
24 | try_increment(handle);
| ------ value moved here
25 | try_increment(handle);
| ^^^^^^ value used here after move
|
为了解决这个问题,我认为通过引用传递 handle
是有意义的。请注意,它应该是一个 immutable 引用,因为我不希望 try_increment
能够更改 handle
本身(将 None
分配给它,例如)只能对其值调用突变。
我的问题是我不知道该怎么做。
这里is the closest working version that I could get:
struct Thing(i32);
impl Thing {
pub fn increment_self(&mut self) {
self.0 += 1;
println!("incremented: {}", self.0);
}
}
fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
// PROBLEM: this line is allowed!
// (*handle) = None;
if let Some(ref mut t) = handle {
t.increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(mut handle: Option<&mut Thing>) {
try_increment(&mut handle);
try_increment(&mut handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
此代码按预期运行,但 Option
现在由 mutable 引用传递,即 not我想要什么:
- 我可以通过将
None
重新分配给它来改变Option
,打破所有后续突变。 (例如,取消注释第 12 行 ((*handle) = None;
)。) - 太乱了:有一大堆无关的
&mut
到处都是。 - 真是一团糟:天知道为什么我必须在
if let
语句中使用ref mut
而惯例是在其他地方使用&mut
。 - 它违背了在编译器中使用复杂的借用检查和可变性检查规则的目的。
有没有什么方法可以真正实现我想要的:通过引用传递一个不可变的 Option
,并实际能够使用它的内容?
您不能从不可变引用中提取可变引用,即使是对其内部结构的引用。这就是重点!不可变引用的多个别名是允许的,因此,如果 Rust 允许您这样做,您可能会遇到两段代码能够同时改变相同数据的情况。
Rust 为 interior mutability 提供了几个逃生口,例如 RefCell
:
use std::cell::RefCell;
fn try_increment(handle: &Option<RefCell<Thing>>) -> bool {
if let Some(t) = handle {
t.borrow_mut().increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(handle: Option<RefCell<Thing>>) {
try_increment(&handle);
try_increment(&handle);
}
fn main() {
let mut thing = RefCell::new(Thing(0));
try_increment_twice(Some(thing));
try_increment_twice(None);
}
TL;DR:答案是否,我不能。
经过与@Peter Hall 和@Stargateur 的讨论,我明白了为什么我需要到处使用 &mut Option<&mut Thing>
。 RefCell<>
也是一个可行的解决方法,但它并不简洁,也没有真正实现我最初寻求实现的模式。
问题是这样的:如果一个人被允许改变一个只有 不可变 引用的对象 Option<&mut T>
一个人可以使用这个权力来破坏完全借用规则。具体来说,本质上,您可以对同一对象有许多 mutable 引用,因为您可以有许多这样的不可变引用。
我知道只有一个可变引用到Thing
(由Option<>
拥有)但是,一旦我开始引用 Option<>
,编译器就不再知道这些引用并不多了。
最佳版型如下:
fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
if let Some(ref mut t) = handle {
t.increment_self();
true
}
else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(mut handle: Option<&mut Thing>) {
try_increment(&mut handle);
try_increment(&mut handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
备注:
Option<>
持有对Thing
的唯一现存可变引用
try_increment_twice()
获得Option<>
的所有权
try_increment()
必须 将Option<>
作为&mut
以便编译器知道它具有对 [= 的唯一可变引用17=], 通话中- 如果编译器知道
try_increment()
具有对Option<>
的唯一可变引用,而Option<>
包含对Thing
的唯一可变引用,则编译器知道借用规则没有被侵犯了。
另一个实验
Option<>
的可变性问题仍然存在,因为可以调用 take()
等。在一个可变的 Option<>
上,打破了后面的一切。
为了实现我想要的模式,我需要 类似 和 Option<>
的东西,但是,即使它是 mutable,它不能被改变。像这样:
struct Handle<'a> {
value: Option<&'a mut Thing>,
}
impl<'a> Handle<'a> {
fn new(value: &'a mut Thing) -> Self {
Self {
value: Some(value),
}
}
fn empty() -> Self {
Self {
value: None,
}
}
fn try_mutate<T, F: Fn(&mut Thing) -> T>(&mut self, mutation: F) -> Option<T> {
if let Some(ref mut v) = self.value {
Some(mutation(v))
}
else {
None
}
}
}
现在,我想,我可以整天传递 &mut Handle
并且知道拥有 Handle
的人只能改变其内容,而不是句柄本身。 (See Playground)
不幸的是,即使这样也没有任何好处,因为如果你有一个可变引用,你总是可以用取消引用运算符重新分配它:
fn try_increment(handle: &mut Handle) -> bool {
if let Some(_) = handle.try_mutate(|t| { t.increment_self() }) {
// This breaks future calls:
(*handle) = Handle::empty();
true
}
else {
println!("warning: increment failed");
false
}
}
一切都很好。
底线结论:只需使用&mut Option<&mut T>