Phantom Data: Type-Safe Configuration with Zero Runtime Cost
Phantom types are a powerful Rust feature allowing you to encode type-level information without incurring any runtime overhead. This challenge asks you to implement a simple configuration system using phantom types to ensure that different configurations are used based on the type of the configuration itself, all while maintaining zero runtime cost. This is useful for scenarios like different API endpoints, database connection strings, or feature flags, where the configuration is determined at compile time.
Problem Description
You are tasked with creating a Config struct that utilizes a phantom type to represent different configuration profiles. The Config struct should contain a single field, value, of type String. The Config struct should be generic over a Profile type, which is a phantom type. You need to implement two functions: create_config and get_value.
create_config<P>(value: String) -> Config<P>: This function takes aStringvalue and creates aConfiginstance associated with the providedProfile.get_value<P>(config: &Config<P>) -> &String: This function takes a reference to aConfiginstance and returns a reference to itsvaluefield.
The goal is to demonstrate that the Profile type is associated with the Config struct at compile time, but does not exist at runtime. The value field should be accessible regardless of the Profile type.
Examples
Example 1:
Input: create_config::<Dev>("Development Value".to_string())
Output: Config<Dev> { value: "Development Value" }
Explanation: A Config instance is created with the "Development Value" string and associated with the `Dev` profile.
Example 2:
Input: let config = create_config::<Prod>("Production Value".to_string()); get_value(&config)
Output: &"Production Value"
Explanation: A Config instance is created with the "Production Value" string and associated with the `Prod` profile. The `get_value` function retrieves a reference to the string.
Example 3: (Edge Case - Different Profiles)
Input: let dev_config = create_config::<Dev>("Dev Value".to_string()); let prod_config = create_config::<Prod>("Prod Value".to_string());
Output: dev_config.value == "Dev Value", prod_config.value == "Prod Value"
Explanation: Demonstrates that different profiles can be associated with different values, and each value is correctly retrieved.
Constraints
- The
Profiletype must be a phantom type. - The
Configstruct must contain a singlevaluefield of typeString. - The
create_configfunction must accept aStringand aProfiletype and return aConfiginstance. - The
get_valuefunction must accept a reference to aConfiginstance and return a reference to thevaluefield. - The solution must compile without warnings.
- There should be no runtime overhead associated with the
Profiletype.
Notes
- Remember that phantom types are not accessible at runtime. They are purely a compile-time mechanism for type checking and ensuring type safety.
- Consider using the
#[repr(transparent)]attribute on yourConfigstruct to ensure zero-sized overhead. - The
Profiletype can be any type, including enums or structs. For simplicity, you can use simple type aliases likeDevandProd.
use std::marker::PhantomData;
struct Config<P> {
value: String,
#[repr(transparent)] _phantom: PhantomData<P>,
}
impl<P> Config<P> {
fn new(value: String) -> Self {
Config {
value,
_phantom: PhantomData,
}
}
}
fn create_config<P>(value: String) -> Config<P> {
Config::new(value)
}
fn get_value<P>(config: &Config<P>) -> &String {
&config.value
}
#[cfg(test)]
mod tests {
use super::*;
type Dev = ();
type Prod = ();
#[test]
fn test_create_config() {
let dev_config = create_config("Development Value".to_string());
assert_eq!(dev_config.value, "Development Value");
let prod_config = create_config("Production Value".to_string());
assert_eq!(prod_config.value, "Production Value");
}
#[test]
fn test_get_value() {
let config = create_config("Test Value".to_string());
assert_eq!(get_value(&config), &"Test Value");
}
#[test]
fn test_different_profiles() {
let dev_config = create_config::<Dev>("Dev Value".to_string());
let prod_config = create_config::<Prod>("Prod Value".to_string());
assert_eq!(dev_config.value, "Dev Value");
assert_eq!(prod_config.value, "Prod Value");
}
}