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?
Is clone good for performance?
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:
#[derive(Clone, Copy)]
struct Color (u8, u8, u8);
pub fn pick_one (param: i32) -> Color {
let index = ...;
Color::PREDEFINED[index]
}
The general rule is to derive Copy
whenever possible.
Is this the right place to use
'static
?
Yes. If you're returning a reference, and its lifetime doesn't belong to one of the parameters, it's pretty much always 'static
.
pub fn pick_one (param: i32) -> &'static Color {
pick_one (&self, param: i32) -> &Color
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 from self
, it should still be a straight Color
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 on array
, helping to reduce code duplication, like:
let c = Color([10, 10, 10]);
let half_c = Color(c.0.map(|n| n / 2));