«Стандартный» способ справиться с возможностью либо String
, либо &str
- это использовать Cow<str>
. COW означает клонирование при записи (или copy -on-write), и вы можете использовать его для других типов, кроме строк. Cow
позволяет вам хранить либо ссылку, либо собственное значение, и клонировать ссылку только в собственное значение, когда вам нужно изменить его.
Есть несколько способов применить это к вашему коду:
- Вы можете просто добавить реализацию
Into<Cow<str>>
, а остальные оставить прежними.
- Измените ваши типы так, чтобы они содержали
Cow<str>
с, чтобы объекты Text
могли содержать принадлежащие String
или &str
.
Первый вариант самый простой. Вы можете просто реализовать эту черту. Обратите внимание, что Into::into
принимает self
, поэтому вам необходимо реализовать это для &Value
, а не Value
, в противном случае заимствованные значения будут ссылаться на собственные значения, которые были использованы into
и уже недействительны.
impl<'a> Into<Cow<'a, str>> for &'a Value {
fn into(self) -> Cow<'a, str> {
match self {
Value::Float(f) => Cow::from(format!("{}", f.value).to_string()),
Value::Text(t) => Cow::from(&t.value),
}
}
}
Реализация этого для &'a Value
позволяет нам связать время жизни в Cow<'a, str>
с источником данных. Это было бы невозможно, если бы мы реализовали только для Value
, что хорошо, потому что данные исчезли бы!
Еще лучшим решением может быть использование Cow
в вашем перечислении Text
:
use std::borrow::Cow;
pub struct Text<'a> {
pub value: Cow<'a, str>,
}
Это позволит вам взять взаймы &str
:
let string = String::From("hello");
// same as Cow::Borrowed(&string)
let text = Text { value: Cow::from(&string) };
или String
:
// same as Cow::Owned(string)
let text = Text { value: Cow::from(string) };
Поскольку Value
теперь может косвенно хранить ссылку, ему потребуется собственный параметр времени жизни:
pub enum Value<'a> {
Float(Float),
Text(Text<'a>),
}
Теперь реализация Into<Cow<str>>
может быть для самого Value
, поскольку ссылочные значения могут быть перемещены:
impl<'a> Into<Cow<'a, str>> for Value<'a> {
fn into(self) -> Cow<'a, str> {
match self {
Value::Float(f) => Cow::from(format!("{}", f.value).to_string()),
Value::Text(t) => t.value,
}
}
}
Так же, как String
, Cow<str>
удовлетворяет Deref<Target = str>
, так что его можно использовать везде, где ожидается &str
, просто передавая ссылку. Это еще одна причина , почему вы всегда должны пытаться принять &str
в аргументе функции , а не String
или &String
.
Как правило, вы можете использовать Cow
с так же удобно, как String
с, потому что они имеют много одинаковых impl
с. Например:
let input = String::from("12.0");
{
// This one is borrowed (same as Cow::Borrowed(&input))
let text = Cow::from(&input);
}
// This one is owned (same as Cow::Owned(input))
let text = Cow::from(input);
// Most of the usual String/&str trait implementations are also there for Cow
let num: f64 = text.parse().unwrap();