为啥 Vec::len 是方法而不是公共属性?

Posted

技术标签:

【中文标题】为啥 Vec::len 是方法而不是公共属性?【英文标题】:Why is Vec::len a method instead of a public property?为什么 Vec::len 是方法而不是公共属性? 【发布时间】:2018-11-12 16:17:40 【问题描述】:

我注意到 Rust 的 Vec::len 方法只是访问向量的 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 中是否有一个很好的指导方针可以遵循?

【问题讨论】:

One question per post, please. 【参考方案1】:

Vec 结构看起来有点像这个[1]:

pub struct Vec<T> 
    ptr: *mut T,
    capacity: usize,
    len: usize,

这个想法是ptr 指向一个大小为capacity 的已分配内存块。如果Vec 的大小需要大于capacity,则分配新内存。已分配内存的未使用部分未初始化,可能包含任意数据。

当您在Vec 上调用变异方法(如pushpop)时,它们会仔细管理Vec 的内部状态,在必要时增加容量,并确保正确删除已删除的项目。

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

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

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


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

【讨论】:

+1 表示其他语言的程序员可能认为设置 len 会改变向量的大小。【参考方案2】:

一个原因是为所有实现某种长度概念的容器提供相同的接口。 (如std::iter::ExactSizeIterator。)

Vec 的情况下,len() 就像一个吸气剂:

impl<T> Vec<T> 
    pub fn len(&self) -> usize 
        self.len
    

虽然这确保了整个标准库的一致性,但这种设计选择还有另一个原因......

这个 getter 防止外部修改 len。如果条件Vec::len &lt;= 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 的要求的方式来设计他们的代码。一个类应该尽可能地作用于它自己的成员。这些行动应该通过方法向公众提供。这里我的意思是做有用的事情的方法——不仅仅是一个简单的返回/设置变量的getter/setter。特别是通过使用构造函数或方法可以使设置器变得多余。 Vec 向我们展示了其中一些“二传手”:

push
insert
pop
reserve
...

因此,Vec 实现了提供对外部世界的访问的算法。但它自己管理它的内部。

【讨论】:

+1 表示 通常是改进程序设计的更好方法,从而使 getter/setter 变得不必要。 99% 的时间,这是构造函数所做的。 我认为这个答案缺少的一个重点是直接公开len 字段会使Vec 不安全(在内存安全意义上),因为需要保留不变量为了不暴露未初始化的内存等。 @fjh 我想说答案,你应该单独添加它 采用良好的指导方针和做法。 (例如,不要直接修改成员。) — 访问器方法这些好习惯!静态类型语言的要点之一,尤其是像 Rust 这样的语言,就是尽可能多地编码到类型系统中,这样你就不必考虑“做正确的事”了。如果我们可以在头脑中编码“良好的指导方针和实践”,我们甚至都不会需要借用检查器或类型,因为我们只需要编写正确的东西! @Shepmaster 我想那是我内心的 Python 谈话(没有真正的 private 字段)。不过,我已经删除了有争议的部分。

以上是关于为啥 Vec::len 是方法而不是公共属性?的主要内容,如果未能解决你的问题,请参考以下文章

为啥类成员是私有的而属性是公共的? [复制]

为啥 DateTime.Now 是属性而不是方法?

Effective Java 第三版——16.在公共类中使用访问方法而不是公共属性

为啥如果我得到计算对象中的对象属性未定义而不是对象本身?哪种方法更适合这种情况?

在JAVA中 string的长度为啥是length方法 而不是length 属性

当我在 IRB 中声明时,为啥我的***方法在所有类上都是公共的(而不是私有的)?