【发布时间】:2018-01-12 12:07:22
【问题描述】:
我一直在阅读诸如Why does a function that accepts a Box<MyType> complain of a value being moved when a function that accepts self works?、Preferable pattern for getting around the "moving out of borrowed self" checker 和How to capture self consuming variable in a struct? 之类的问题,现在我很好奇消费 self 但可能会将其返回给调用者的性能特征。
举一个更简单的例子,假设我想创建一个保证非空的集合类型。为此,“删除”操作需要消耗集合并可选择返回自身。
struct NonEmptyCollection { ... }
impl NonEmptyCollection {
fn pop(mut self) -> Option<Self> {
if self.len() == 1 {
None
} else {
// really remove the element here
Some(self)
}
}
}
(我想它也应该返回它从列表中删除的值,但这只是一个例子。)现在假设我调用了这个函数:
let mut c = NonEmptyCollection::new(...);
if let Some(new_c) = c.pop() {
c = new_c
} else {
// never use c again
}
对象的内存实际上发生了什么?如果我有一些类似的代码怎么办:
let mut opt: Option<NonEmptyCollection> = Some(NonEmptyCollection::new(...));
opt = opt.take().pop();
函数的签名并不能保证返回的对象实际上是同一个,那么可以进行哪些优化呢? C++ 返回值优化之类的东西是否适用,允许返回的对象在它之前所在的同一内存中“构造”?如果我可以在上述接口和调用者必须处理生命周期的接口之间进行选择:
enum PopResult {
StillValid,
Dead
};
impl NonEmptyCollection {
fn pop(&mut self) -> PopResult {
// really remove the element
if self.len() == 0 { PopResult::Dead } else { PopResult::StillValid }
}
}
出于性能原因,有没有理由选择这个更脏的界面?在answer to the second example I linked 中,trentcl 建议将Options 存储在数据结构中,以允许调用者就地进行更改,而不是每次都在remove 后面跟着insert。这个肮脏的界面会是一个更快的选择吗?
【问题讨论】:
-
与任何类型的任何其他变量相比,
self没有什么特别之处。