我用一个例子来说明这个一般性问题:我有一个
Color
-Struct,它包含三个 u8
(红、绿、蓝)。 Color
有一个关联的常量 Color::PREDEFINED
和一些预定义的颜色。我需要一个关联函数 Color::pick_one
返回这些预定义颜色之一,具体取决于给定参数:
#[derive(Clone)]
struct Color (u8, u8, u8);
impl Color {
const PREDEFINED: [Color; 3] = [
Color(90, 250, 10),
Color(120, 10, 10),
Color(40, 10, 200)
];
pub fn pick_one (param: i32) -> Color {
let index = // do some math with `param`.
Color::PREDEFINED[index]
}
pub fn to_string (&self) -> String {
format!("rgb({}, {}, {})", self.0, self.1, self.2)
}
}
现在这显然不起作用,因为
pick_one
从 Color
返回一个 PREDEFINED
,但是我们 cannot move out of type [Color; 3], a non-copy array
.
一个解决方案可能是克隆返回的颜色:
Color::PREDEFINED[index].clone()
但这是好的表现吗?
如果
pick_one
返回一个 &Color
而不是 Color
,我也可以接受,但是:
pub fn pick_one (param: i32) -> &Color {
let index = // do some math with `param`.
&Color::PREDEFINED[index]
}
给:
missing lifetime specifier
this function's return type contains a borrowed value, but there is no value for it to be borrowed from
consider using the `'static` lifetime: `'static `
现在,这是正确的地方使用
'static
吗?
我发现它实际上令人困惑
pub fn pick_one (&self, param: i32) -> &Color {
let index = // do some math with `param`.
&Color::PREDEFINED[index]
}
效果很好——这看起来有点像 3rd lifetime elision rule,对吧?但是这个规则的想法是不是:在删除
self
之后不能调用方法,因此,返回对self
字段的引用总是有效的?但这不是这里的重点,因为 PREDEFINED
不是 self
的字段,而是与 Color
本身相关联,只要 PREDEFINED
存在(即始终存在),对 Color
的引用就有效。并且使 pick_one
成为一种方法实际上毫无意义,因为总是调用 Color(0, 0, 0).pick_one(12)
在技术上是可行的,但从语义的角度来看没有意义。
现在,实现这样一个从关联常量返回值的关联函数的最佳方法是什么?
克隆对性能有好处吗?
复制 3 个字节非常便宜。这几乎总是比引用更高效。这种廉价是
Copy
存在的原因,你应该在这里使用它:
#[derive(Clone, Copy)]
struct Color (u8, u8, u8);
pub fn pick_one (param: i32) -> Color {
let index = ...;
Color::PREDEFINED[index]
}
一般规则是尽可能导出
Copy
。
这是正确的地方使用
吗?'static
是的。如果你要返回一个引用,并且它的生命周期不属于参数之一,它几乎总是
'static
.
pub fn pick_one (param: i32) -> &'static Color {
pick_one (&self, param: i32) -> &Color
如果您想在未来的版本中保留从
self
产生回报的能力而不破坏您的 API,则此方法才有意义。然而,这也只对非Copy
类型有意义,所以即使你从self
产生回报,它仍然应该是一个没有参考的直Color
。
旁注:当你有一个所有相同类型的元组时,考虑做
struct Color([u8; 3])
。这在内存表示中是等效的,并为您提供array
上的所有方法,有助于减少代码重复,例如:
let c = Color([10, 10, 10]);
let half_c = Color(c.0.map(|n| n / 2));