为什么 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 上调用变异方法时,例如 pushpop,它们会仔细管理 Vec 的内部状态,在必要时增加容量,并确保删除的项目已正确删除。

如果 len 是一个 public 字段,任何具有自有 Vec 或对其可变引用的代码都可以将 len 设置为任何值。将其设置得高于应有的值,您将能够从未初始化的内存中读取,从而导致 未定义的行为 。将它设置得更低,您将有效地删除元素而不会正确删除它们。

在其他一些编程语言中(例如 JavaScript),数组或向量的 API 专门允许您通过设置 length 属性 来更改大小。认为习惯了这种方法的程序员可能会在 Rust 中意外地这样做并不是没有道理的。

将所有字段保持私有并为 len() 使用 getter 方法允许 Vec 保护其内部的可变性,提供强大的内存保证并防止用户意外做坏事自己做事。


[1] 实际上,在这个数据结构上构建了抽象层,所以它 looks a little different.