I illustrate this general question with an example: I have a Color-Struct which holds three u8s (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
Copyexists, and you should use it here:The general rule is to derive
Copywhenever 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
selfin a future version without breaking your API. However, this also only makes sense for non-Copytypes, so even if you produce the return fromself, it should still be a straightColorwith 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: