Hone logo
Hone
Problems

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 a String value and creates a Config instance associated with the provided Profile.
  • get_value<P>(config: &Config<P>) -> &String: This function takes a reference to a Config instance and returns a reference to its value field.

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 Profile type must be a phantom type.
  • The Config struct must contain a single value field of type String.
  • The create_config function must accept a String and a Profile type and return a Config instance.
  • The get_value function must accept a reference to a Config instance and return a reference to the value field.
  • The solution must compile without warnings.
  • There should be no runtime overhead associated with the Profile type.

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 your Config struct to ensure zero-sized overhead.
  • The Profile type can be any type, including enums or structs. For simplicity, you can use simple type aliases like Dev and Prod.
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");
    }
}
Loading editor...
rust