Doing some experiments with newtypes and looking for the most efficient, most ergonomic way of dealing with converting elements in collections. For singular values, pretty standard type conversion traits seem to work just fine:
pub struct Tag(String);
impl From<Tag> for String {
fn from(v: Tag) -> String {
v.0
}
}
impl From<String> for Tag {
fn from(v: String) -> Tag {
Tag(v)
}
}
impl AsRef<String> for Tag {
fn as_ref(&self) -> &String {
&self.0
}
}
impl<'a> From<&'a Tag> for String {
fn from(t: &Tag) -> String {
t.0.clone()
}
}
But, I start to hit issues when I want to deal with lists of items. Say (for the purposes of illustration) I have a function which processes tags, and some more abstract function which deals with writing strings to a DB:
fn process_item(tags: &Vec<Tag>) {
process_array_of_strings(tags);
}
fn process_array_of_strings(strings: &Vec<String>) {
// ...
}
This won't compile because Rust can't coerce tags
to a Vec<String>
, and it feels to me like there should be some way of doing this more easily than:
fn process_item(tags: &Vec<Tag>) {
let str_tags: Vec<String> = tags.iter().map(|t| t.into()).collect();
process_array_of_strings(&str_tags);
}
...which besides being verbose, involves much more intermediary memory than I'd like. The reverse conversion is also an issue, but presumably would be implemented in the same way.
There may be some additional things in play here, for example whether I should be sending Vec
s of references rather than values, and whether or not it's a good idea to implement type conversions from reference types.
Simple, just use generic:
pub struct Tag(String);
impl AsRef<str> for Tag {
fn as_ref(&self) -> &str {
&self.0
}
}
fn process_item(tags: &[Tag]) {
process_array_of_strings(tags);
}
fn process_array_of_strings<'a, T>(strings: &[T])
where
T: AsRef<str>,
{
// ...
}
T
implement AsRef<str>
(more generic than String
) so you can just use as_ref()
after on it.