我注意到Rust的Vec::len
方法只访问了vector len
属性。为什么不是len
只是一个公共财产,而不是围绕它包装一个方法?我假设这是为了在以后实现发生变化的情况下,没有什么会破坏,因为Vec::len
可以改变它获取长度的方式,而Vec
的任何用户都不知道。但我不知道是否还有其他原因。
我的问题的第二部分是关于我何时设计API。如果我正在构建自己的API,并且我有一个具有len
属性的结构,那么我应该将len
设为私有并创建公共len()
方法吗?在Rust中公开字段是不好的做法吗?我不这么认为,但我没有注意到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中有一个很好的指导方针吗?
答案 0 :(得分:8)
一个原因是为实现某种长度概念的所有容器提供相同的接口。 (例如std::iter::ExactSizeIterator
。)
在Vec
的情况下,len()
就像一个吸气剂:
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绝对是好的(疯狂的人可能会尝试修改它!)。
但是,应该以最小化getter / setter要求的方式设计他们的代码。一个班级应该尽可能地对自己的成员采取行动。应通过方法向公众提供这些行动。在这里,我指的是做有用的事情的方法 - 而不仅仅是一个普通的&#39;返回/设置变量的getter / setter。特别是通过使用构造函数或方法可以使setter变得多余。 Vec
向我们展示了其中一些&#34;制定者&#34;:
push
insert
pop
reserve
...
因此,Vec
实现了提供对外界访问的算法。但它本身就是管理它的内脏。
答案 1 :(得分:3)
Vec
结构看起来类这个 [1] :
pub struct Vec<T> {
ptr: *mut T,
capacity: usize,
len: usize,
}
这个想法是ptr
指向一个大小为capacity
的已分配内存块。如果Vec
的大小需要大于capacity
,则分配新内存。分配的内存的未使用部分未初始化,可能包含任意数据。
当您在Vec
或push
pop
上调用变异方法时,他们会仔细管理Vec
的内部状态,在必要时增加容量,并确保项目被删除的都被正确删除。
如果len
是公共字段,则拥有Vec
的所有代码或对其中一个的可变引用可以将len
设置为任何值。将其设置为高于应有的值,您将能够从未初始化的内存中读取,从而导致未定义的行为。将它设置得更低,您将有效地删除元素而不会正确删除它们。
在其他一些编程语言(例如JavaScript)中,数组或向量的API专门允许您通过设置length
属性来更改大小。认为习惯这种方法的程序员可以在Rust中意外地做到这一点并不是不合理的。
保持所有字段的私有性并使用len()
的getter方法允许Vec
保护其内部的可变性,提供强大的内存保证并防止用户意外地为自己做坏事。
[1]实际上,在这个数据结构上构建了抽象层,因此looks a little different。