I illustrate this general question with an example: I have a Color
-Struct which holds three u8
s (Red, Green, Blue). Color
has an assosiated constant Color::PREDEFINED
with some predefined colors. I need an assosiated function Color::pick_one
returning one of these predefined colors, depending on a given parameter:
#[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)
}
}
Now this obviously doesn't work since pick_one
returns a Color
from PREDEFINED
, but we cannot move out of type [Color; 3], a non-copy array
.
A solution could be to clone the returned Color:
Color::PREDEFINED[index].clone()
but is this good performance?
I would also be fine with pick_one
returning a &Color
instead of a Color
, but:
pub fn pick_one (param: i32) -> &Color {
let index = // do some math with `param`.
&Color::PREDEFINED[index]
}
gives:
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 `
Now, is this the right place to use 'static
?
I find it actually confusing that
pub fn pick_one (&self, param: i32) -> &Color {
let index = // do some math with `param`.
&Color::PREDEFINED[index]
}
works quite well – this looks a bit like the 3rd lifetime elision rule, right? But isn't the idea of this rule: A methos can't be called after self
was dropped, thus, returned references to fields of self
is always valid? But this isn't the point here, since PREDEFINED
is not a field of self
, but assosiated to Color
itself, making references to PREDEFINED
be valid as long as Color
exists (i.e. always). And making pick_one
a method is actually pointless, since always calling Color(0, 0, 0).pick_one(12)
is technically possible, but doesn't make sense from a semantic point of view.
Now, what is the best way to implement such an assosiated function returning a value from an assosiated constant?
Copying 3 bytes is extremely cheap. This will almost always be more performant than making a reference. This cheapness is why
Copy
exists, and you should use it here:The general rule is to derive
Copy
whenever possible.Yes. If you're returning a reference, and its lifetime doesn't belong to one of the parameters, it's pretty much always
'static
.This one only makes sense if you want to reserve the ability of producing the return from
self
in a future version without breaking your API. However, this also only makes sense for non-Copy
types, so even if you produce the return fromself
, it should still be a straightColor
with no reference.Side note: when you have a tuple of all the same type, consider doing
struct Color([u8; 3])
. This is equivalent in memory representation and gives you all the methods onarray
, helping to reduce code duplication, like: