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,
TagandTagBufandimpl ToOwned for Tagsimilar tostd::path::PathandPathBuf. The downside is that it will require basically duplicating most of the methods plus some boilerplate for the conversion andunsafe std::mem::transmutefor casting the&strto&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_ownedreturningTag<'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(IIUCDerefshould make the methods available onStringdirectly).
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?