I need a type (called Tag
) that is represented with a string, but the possible values are restricted and there is a bunch of special functions for it, so it makes a lot of sense to create a new type for it. Additionally it makes a lot of sense to have both borrowed and owned instances, because on one hand many instances will be read-only created from references into a larger string (so I can't just use &Tag
) while on the other there are functions for composing the value and these need to store the result.
So I can think of four options:
-
Create two variants,
Tag
andTagBuf
andimpl ToOwned for Tag
similar tostd::path::Path
andPathBuf
. The downside is that it will require basically duplicating most of the methods plus some boilerplate for the conversion andunsafe std::mem::transmute
for casting the&str
to&Tag
. And then some functions will end up returningCow<'a, Tag>
anyway, because they may need to normalize the content. -
Wrap
Cow<'a, str>
likestruct Tag<'a> { inner: Cow<'a, str> }
. This would implementinto_owned
returningTag<'static>
. That looks relatively nice to me, minimizing the duplication at the cost of worse control over memory allocation. I have not seen any precedent for this though, so I am not sure I won't hit a roadblock. -
Give up on efficiency and wrap a
String
. -
Give up on new type and create a trait and implement it for
str
(IIUCDeref
should make the methods available onString
directly).
Given the last note, Deref
should make all the read-only methods of Tag
available on TagBuf
and Cow<'a, Tag>
(that I would need a lot; some operations normalize and would clone if needed) in the first option too, wouldn't it?
So what would be the most Rusty approach?