我有一个CPU
结构,其中包含load_rom
方法:
use std::fs::File;
use std::io::{self, Read};
pub struct CPU {
pub mem: [u8; 4096],
V: [u8; 16],
I: u16,
stack: [u16; 16],
opcode: u16,
}
impl CPU {
pub fn new() -> CPU {
CPU {
mem: [0; 4096],
V: [0; 16],
I: 0,
stack: [0; 16],
opcode: 0,
}
}
pub fn load_rom(&self, filepath: &str) {
let mut rom: Vec<u8> = Vec::new();
let mut file = File::open(filepath).unwrap();
file.read_to_end(&mut rom);
for (i, mut byte) in rom.iter().enumerate() {
self.mem[i] = *byte;
}
}
}
fn main() {}
这会产生错误:
error: cannot assign to immutable indexed content `self.mem[..]`
--> src/main.rs:28:13
|
28 | self.mem[i] = *byte;
| ^^^^^^^^^^^^^^^^^^^
当我使用let mut cpu = CPU::new();
创建CPU并将&mut self
传递给load_rom
方法时,一切正常。
如果我在创作时不使用mut
,我会收到错误:
error: cannot borrow immutable local variable `cpu` as mutable
--> src/main.rs:10:2
|
9 | let cpu = CPU::new();
| --- use `mut cpu` here to make mutable
10 | cpu.load_rom("/Users/.../Code/Rust/chip8/src/roms/connect4.ch8");
| ^^^ cannot borrow mutably
我必须使cpu
可变,以便内部函数修改自己的内容,这似乎是对的。我真的必须声明cpu
是可变的吗?或者我错过了什么?
答案 0 :(得分:4)
使
cpu
变为可变,以便内部函数修改自己的内容
(强调我的)
Rust是系统语言,这意味着它会尝试让您能够创建 fast 和高效代码。完成此任务的主要方法之一是提供对现有数据的引用,而不是复制它。
Rust也是安全语言,其中(除其他外)意味着访问无效引用应该是不可能的。
要实现这两个目标,必须进行权衡。某些语言将安全检查移至运行时,强制执行强制同步原语(例如互斥和朋友)或其他一些有趣的解决方案。有些语言完全避免混乱,选择不允许引用或不试图保证安全。
Rust通过在编译时检查尽可能多的东西而与这些不同。这意味着编译器有能够推断出一块内存可能会发生变异的时间和地点。
如果它不知道这一点,那么你可能会获得对某个值内某些内容的引用,然后对该值调用一个使该引用无效的变异方法。当你去使用现在无效的引用... BOOOOOM 。您的程序在最佳崩溃,或泄漏信息或在最差创建后门。
&mut self
指示编译器此方法可能会改变其中的值。获取对已经可变的值的可变引用是有效的,这由变量绑定的mut
关键字表示(此处为mut cpu
)。
但是,这对于编译器来说并不是 。知道某些事情正在被改变,对于程序员来说,高度也很有价值。大型系统中的可变性增加了难以理解的复杂性,并且被迫明确地列出某些东西是不可变的,可以提供非常丰富的信息和精神上的自由。
了解Rust适用的rules for borrowing也很有用。这些限制你的一个或另一个:
* one or more references (`&T`) to a resource,
* exactly one mutable reference (`&mut T`).
简洁地说,这可以归结为&#34;别名XOR可变性&#34;。
如果您的变异确实是内部变异,那么您也可以使用interior mutability,例如使用RefCell
或Mutex
。您使用的内容取决于您的需求以及您要存储的数据类型。
这些结构非常适合像缓存这样的结构,在这些结构中你可以隐藏&#34;隐藏&#34;来自外部的可变性。但是,这些也存在局限性,因为中对数据的引用的生命周期必须缩短,以继续提供&#34;别名XOR可变性&#34;保证保持代码安全。
对于您的具体问题,我同意评论者认为load_rom
接受&mut self
是有意义的。它甚至可以简化:
pub fn load_rom(&mut self, filepath: &str) {
let mut file = File::open(filepath).unwrap();
file.read_exact(&mut self.mem);
}
您可能希望在加载前将所有旧数据清零。否则,如果加载的第二个ROM小于第一个ROM,则第一个ROM中的数据可能会泄漏到第二个ROM(an actual bug from older computers / operating systems)。
答案 1 :(得分:2)
Rust使用传递不变性模型。这意味着如果变量标记为immutable
,则变量可能不会变异,并且通过变量访问的数据可能不会变异。
此外,如果您对变量有可变引用,则类型系统不允许共存的任何不可变引用;因此,未标记为“mut”的数据在不可变引用的整个生命周期中都是真正不变的。
这使得它在默认情况下不可能同时对同一数据进行两次可变引用。这是有效的存储器安全性和线程安全性的要求;并且还可以更简单地推理Rust代码中的变异。
如果您想要“内部”可变性,可以使用Cell<T>
模块中的RefCell<T>
或std::cell
。但是,这对于CPU结构来说可能是错误的,该结构旨在表示预期运行的CPU,并在每次操作后更改其状态。内部可变性通常应保留用于在不执行对象的任何逻辑(外部可见)突变的操作的实现内执行突变。运行操作或加载内存的CPU不适合这样做,因为诸如“加载内存”,“运行指令”等任何操作都会改变CPU的逻辑状态。
有关何时需要内部可变性的进一步讨论,请参阅the std::cell
documentation。