At work, we have this marker struct used to prove certain setup have finished, and functions depending on these setups would take this empty marker struct as a parameter. This allows us to prove invalid states are impossible at compile time, helping mitigating invalid usages. This struct being empty also allowed it to be optimized away by the compiler so you pay 0 extra runtime cost. Pretty neat!
#[derive(Clone, Copy)]
struct ThingsHappened;
fn init_things() -> ThingsHappened;
fn foo(_things: ThingsHappened) {
// do things
}
I was reading some code last week, and I oversaw something like this:
use std::sync::OnceLock;
static MARKER: OnceLock<ThingsHappened> = OnceLock::new();
fn main(marker: ThingsHappened) {
MARKER.set(marker).ok();
}
fn do_things() -> ThingsHappened {
foo(MARKER.get().unwrap());
}
This is a bit unfortunate and defeats the purpose of a marker struct. It probably added some unintended runtime overhead for something that could have been free. Unfortunate, but not the end of the world.
This reminds me of another code I read the other day:
// client.rs
struct DatabaseServiceClient;
impl DatabaseServiceClient {
fn call_foo() -> Self {}
}
// service.rs
struct ApplicationService<'a> {
database_client: &'a DatabaseServiceClient,
}
It appears to be trying to do the most efficient thing by borrowing the client in the service without making an extra copy, and with correct lifetime management. However, if you look closer, it is borrowing a struct that is completely empty. There is no actual cost when copying the empty DatabaseServiceClient
. The lifetime annotation in ApplicationService
is not necessary, and it also has caused many other issues when the team tried to add some async code in the service. That are many engineering hours wasted for little to nothing benefit.
It may still make some sense when someone wants to add some state to DatabaseServiceClient
in the future, but in this specific case, the chance is quite low. It would have been obvious this extra cost coming from doing the smart thing is not worth it.
This seems to be a common pattern among Rust learners. I am sure I have written similar code at some point in my life. It's 100% fine to experiment with shiny new language features, but it is also important to recognize appropriate use cases for them. Hope this helps.