为什么 Vec::len 是方法而不是 public 属性?
Why is Vec::len a method instead of a public property?
我注意到 Rust 的 Vec::len
方法只是访问向量的 len
属性。为什么 len
不只是一个 public 属性,而不是围绕它包装一个方法?
我假设这是为了万一将来实现发生变化,不会有任何问题,因为 Vec::len
可以在 Vec
的任何用户不知情的情况下改变它获取长度的方式,但我不知道还有没有别的原因。
我问题的第二部分是关于我何时设计 API。如果我正在构建自己的 API,并且我有一个带有 len
属性 的结构,我应该将 len
设为私有并创建一个 public len()
方法?在 Rust 中创建字段 public 是不好的做法吗?我不这么认为,但我没有注意到在 Rust 中经常这样做。例如,我有以下结构:
pub struct Segment {
pub dol_offset: u64,
pub len: usize,
pub loading_address: u64,
pub seg_type: SegmentType,
pub seg_num: u64,
}
这些字段中的任何一个是否应该是私有的,而是像 Vec
那样具有包装函数?如果是这样,那为什么呢?在 Rust 中是否有一个好的指南可以遵循?
一个原因是为实现某种长度概念的所有容器提供相同的接口。 (如std::iter::ExactSizeIterator
.)
在 Vec
的情况下,len()
的行为类似于 getter:
impl<T> Vec<T> {
pub fn len(&self) -> usize {
self.len
}
}
虽然这确保了整个标准库的一致性,但这种设计选择背后还有另一个原因...
此 getter 防止 len
的外部修改。如果条件 Vec::len <= Vec::buf::cap
从未被满足,Vec
的方法可能会尝试非法访问内存。例如, Vec::push
:
的实现
pub fn push(&mut self, value: T) {
if self.len == self.buf.cap() {
self.buf.double();
}
unsafe {
let end = self.as_mut_ptr().offset(self.len as isize);
ptr::write(end, value);
self.len += 1;
}
}
将尝试写入超过容器拥有的内存的实际末端的内存。由于此关键要求,禁止修改 len
。
哲学
在库代码中使用这样的 getter 肯定很好(外面的疯子可能会尝试修改它!)。
但是,人们应该以一种最小化 getters/setters 要求的方式设计他们的代码。 A class 应该尽可能作用于它自己的成员。这些操作应该通过方法提供给 public。在这里,我指的是 做有用事情的方法 -- 不仅仅是一个普通的 getter/setter returns/sets 变量。尤其是 Setter 可以通过使用构造函数或方法变得多余。 Vec
向我们展示了其中的一些 "setters":
push
insert
pop
reserve
...
因此,Vec
实现了提供对外部世界的访问的算法。但是它自己管理它的内部。
Vec
结构看起来有点像 这个[1]:
pub struct Vec<T> {
ptr: *mut T,
capacity: usize,
len: usize,
}
想法是 ptr
指向大小为 capacity
的已分配内存块。如果 Vec
的大小需要大于 capacity
则分配新的内存。已分配内存的未使用部分未初始化,可能包含任意数据。
当您在 Vec
上调用变异方法时,例如 push
或 pop
,它们会仔细管理 Vec
的内部状态,在必要时增加容量,并确保删除的项目已正确删除。
如果 len
是一个 public 字段,任何具有自有 Vec
或对其可变引用的代码都可以将 len
设置为任何值。将其设置得高于应有的值,您将能够从未初始化的内存中读取,从而导致 未定义的行为 。将它设置得更低,您将有效地删除元素而不会正确删除它们。
在其他一些编程语言中(例如 JavaScript),数组或向量的 API 专门允许您通过设置 length
属性 来更改大小。认为习惯了这种方法的程序员可能会在 Rust 中意外地这样做并不是没有道理的。
将所有字段保持私有并为 len()
使用 getter 方法允许 Vec
保护其内部的可变性,提供强大的内存保证并防止用户意外做坏事自己做事。
[1] 实际上,在这个数据结构上构建了抽象层,所以它 looks a little different.
我注意到 Rust 的 Vec::len
方法只是访问向量的 len
属性。为什么 len
不只是一个 public 属性,而不是围绕它包装一个方法?
我假设这是为了万一将来实现发生变化,不会有任何问题,因为 Vec::len
可以在 Vec
的任何用户不知情的情况下改变它获取长度的方式,但我不知道还有没有别的原因。
我问题的第二部分是关于我何时设计 API。如果我正在构建自己的 API,并且我有一个带有 len
属性 的结构,我应该将 len
设为私有并创建一个 public len()
方法?在 Rust 中创建字段 public 是不好的做法吗?我不这么认为,但我没有注意到在 Rust 中经常这样做。例如,我有以下结构:
pub struct Segment {
pub dol_offset: u64,
pub len: usize,
pub loading_address: u64,
pub seg_type: SegmentType,
pub seg_num: u64,
}
这些字段中的任何一个是否应该是私有的,而是像 Vec
那样具有包装函数?如果是这样,那为什么呢?在 Rust 中是否有一个好的指南可以遵循?
一个原因是为实现某种长度概念的所有容器提供相同的接口。 (如std::iter::ExactSizeIterator
.)
在 Vec
的情况下,len()
的行为类似于 getter:
impl<T> Vec<T> {
pub fn len(&self) -> usize {
self.len
}
}
虽然这确保了整个标准库的一致性,但这种设计选择背后还有另一个原因...
此 getter 防止 len
的外部修改。如果条件 Vec::len <= Vec::buf::cap
从未被满足,Vec
的方法可能会尝试非法访问内存。例如, Vec::push
:
pub fn push(&mut self, value: T) {
if self.len == self.buf.cap() {
self.buf.double();
}
unsafe {
let end = self.as_mut_ptr().offset(self.len as isize);
ptr::write(end, value);
self.len += 1;
}
}
将尝试写入超过容器拥有的内存的实际末端的内存。由于此关键要求,禁止修改 len
。
哲学
在库代码中使用这样的 getter 肯定很好(外面的疯子可能会尝试修改它!)。
但是,人们应该以一种最小化 getters/setters 要求的方式设计他们的代码。 A class 应该尽可能作用于它自己的成员。这些操作应该通过方法提供给 public。在这里,我指的是 做有用事情的方法 -- 不仅仅是一个普通的 getter/setter returns/sets 变量。尤其是 Setter 可以通过使用构造函数或方法变得多余。 Vec
向我们展示了其中的一些 "setters":
push
insert
pop
reserve
...
因此,Vec
实现了提供对外部世界的访问的算法。但是它自己管理它的内部。
Vec
结构看起来有点像 这个[1]:
pub struct Vec<T> {
ptr: *mut T,
capacity: usize,
len: usize,
}
想法是 ptr
指向大小为 capacity
的已分配内存块。如果 Vec
的大小需要大于 capacity
则分配新的内存。已分配内存的未使用部分未初始化,可能包含任意数据。
当您在 Vec
上调用变异方法时,例如 push
或 pop
,它们会仔细管理 Vec
的内部状态,在必要时增加容量,并确保删除的项目已正确删除。
如果 len
是一个 public 字段,任何具有自有 Vec
或对其可变引用的代码都可以将 len
设置为任何值。将其设置得高于应有的值,您将能够从未初始化的内存中读取,从而导致 未定义的行为 。将它设置得更低,您将有效地删除元素而不会正确删除它们。
在其他一些编程语言中(例如 JavaScript),数组或向量的 API 专门允许您通过设置 length
属性 来更改大小。认为习惯了这种方法的程序员可能会在 Rust 中意外地这样做并不是没有道理的。
将所有字段保持私有并为 len()
使用 getter 方法允许 Vec
保护其内部的可变性,提供强大的内存保证并防止用户意外做坏事自己做事。
[1] 实际上,在这个数据结构上构建了抽象层,所以它 looks a little different.