Trying to implement a ResourceId
abstraction in Rust such that:
- for a given resource kind, the prefix of the ID is constrained at compile time
- for a given resource kind, the size in bytes for that ID is constrained at compile time
- resource ID for resource kind A is not compatible with resource ID for resource kind B also enforced at compile time.
Before compiling anything, I came up with:
pub struct ResourceId<T: ResourceKind>([u8; T::SIZE], PhantomData<T>);
trait ResourceKind {
const SIZE: usize;
const PREFIX: &'static str;
}
impl<T: ResourceKind> Display for ResourceId<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let encoded_bytes = /* encode self.0 to a string */
write!(f, "{}_{}", T::PREFIX, encoded_bytes)
}
}
Now, let’s implement the resource ID for a resource Foo
:
pub struct Foo;
impl ResourceKind for Foo {
const SIZE: usize = 8;
const PREFIX: &'static str = "foo";
}
Now, in my code, wherever I want to operate on a Foo
resource, I want to accept a ResourceId<Foo>
ensuring a resource ID of another prefix than “foo” or another byte size than “8” would not compile.
I was obviously greeted by a number of errors related to the experimental feature generic_const_exprs
.
Another alternative I thought of:
- change
ResourceId
to be aResourceId<T: ResourceKind, const SIZE: usize>
but this would effectively allow one to define an ID for a resource R with multiple acceptable sizes in bytes as they would simply become independent
What would be an acceptable and (hopefully) idiomatic code design for this without relying on a nightly compiler and generic_const_exprs
?