为啥 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
上调用变异方法(如push
或pop
)时,它们会仔细管理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 <= 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 是方法而不是公共属性?的主要内容,如果未能解决你的问题,请参考以下文章
Effective Java 第三版——16.在公共类中使用访问方法而不是公共属性
为啥如果我得到计算对象中的对象属性未定义而不是对象本身?哪种方法更适合这种情况?