Hone logo
Hone
Problems

Implementing Phantom Data in Rust

This challenge focuses on understanding and utilizing Rust's "phantom data" mechanism. Phantom data is a powerful technique for associating a type parameter with a struct without actually holding a value of that type. This is useful for encoding information about types at compile time that doesn't require runtime memory allocation, such as enforcing certain invariants or controlling behavior based on type parameters.

Problem Description

Your task is to implement a generic data structure that demonstrates the use of phantom data. Specifically, you will create a Wrapper<T, PhantomType> struct. This struct will conceptually hold a value of type T but will use a phantom type parameter PhantomType to influence its behavior or represent some compile-time metadata.

You will need to:

  1. Define a Wrapper struct that is generic over two type parameters: T (the type of the data it "wraps") and PhantomType (the phantom type).
  2. Ensure that Wrapper<T, PhantomType> only contains a field of type T. The PhantomType should not occupy runtime memory.
  3. Implement a new function to create instances of Wrapper. This function will take a value of type T and return a Wrapper with the specified PhantomType.
  4. Implement a method get_data that returns a reference to the inner data of type T.
  5. Implement a method mark_as that conceptually "changes" the phantom type of the wrapper. This method should not allocate any new memory and should return a new Wrapper with the same T but a different PhantomType.
  6. Create distinct "marker" types (e.g., struct Valid;, struct Invalid;) to be used as PhantomType.

Examples

Example 1:

struct Valid;
struct Invalid;

// Assume Wrapper and its methods are implemented as per the problem description

let valid_data = Wrapper::<i32, Valid>::new(10);
assert_eq!(valid_data.get_data(), &10);

let invalid_data = valid_data.mark_as::<Invalid>();
assert_eq!(invalid_data.get_data(), &10);

Explanation: An i32 value 10 is wrapped with the Valid phantom type. We can retrieve the inner data. Then, we "re-mark" it as Invalid without changing the underlying i32 value.

Example 2:

struct Enabled;
struct Disabled;

// Assume Wrapper and its methods are implemented as per the problem description

let enabled_feature = Wrapper::<String, Enabled>::new("MyFeature".to_string());
assert_eq!(enabled_feature.get_data(), "MyFeature");

let disabled_feature = enabled_feature.mark_as::<Disabled>();
assert_eq!(disabled_feature.get_data(), "MyFeature");

Explanation: A String is wrapped with the Enabled phantom type. We then use mark_as to create a new Wrapper conceptually representing the same feature but in a Disabled state, all without altering the string data itself or incurring extra memory costs for the phantom types.

Constraints

  • The Wrapper struct must adhere to the principle that PhantomType does not occupy runtime memory. This means it should not have any fields.
  • The mark_as method must return a new Wrapper instance and must not allocate memory for the phantom type itself.
  • Input values for T can be any type for which ownership can be transferred or borrowed.

Notes

Think about how Rust handles type parameters and the PhantomData struct from the standard library. PhantomData<T> is the idiomatic way to represent phantom types. Consider how PhantomData allows you to associate type information without runtime cost. The mark_as method is key to demonstrating the compile-time nature of phantom types – you're essentially telling the compiler to treat the Wrapper instance as having a different phantom type.

Loading editor...
rust