[英]How to build a flexible multiple type data system in Rust without cloning strings?
我想构建一个系统,在该系统中,不同类型的数据( i32
, String
,...)在修改数据的函数之间流动。 例如,我想要一个add
函数来获取“一些”数据并将其添加。
add
函数获得类型为Value
东西,如果Value
是i32
,它将两个i32
值相加,如果它是String
,则返回一个将两个字符串组合在一起的字符串。
我知道这对于模板编程(或在Rust中所说的任何东西,我来自C ++)来说几乎是完美的,但就我而言,我希望有一些小的代码块来处理这些东西。
例如,使用f64
和String
,使用Float
和Text
作为名称,我有:
pub struct Float {
pub min: f64,
pub max: f64,
pub value: f64,
}
pub struct Text {
pub value: String,
}
pub enum Value {
Float(Float),
Text(Text),
}
现在,我想实现一个函数,该函数获取应该是字符串的值并对其执行某些操作,因此我为Value
实现了to_string()
方法:
impl std::string::ToString for Value {
fn to_string(&self) -> String {
match self {
Value::Float(f) => format!("{}", f.value).to_string(),
Value::Text(t) => t.value.clone(),
}
}
}
现在,该函数将执行以下操作:
fn do_something(value: Value) -> Value {
let s = value.to_string();
// do something with s, which probably leads to creating a new string
let new_value = Text(new_string);
Value::Text(new_value)
}
在Value::Float
的情况下,这将创建一个新的String
,然后在结果中返回一个新的String
并将其返回,但是在Value::Text
的情况下,这将克隆该字符串,这是不必要的步骤,并且然后创建新的。
有没有一种方法, to_string()
实现可以在Value::Float
上创建一个新的String
,但是返回Value::Text
的值的引用?
处理String
或&str
可能性的“标准”方法是使用Cow<str>
。 COW代表写时克隆(或写时复制 ),您可以将其用于字符串以外的其他类型。 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
实现,这是不可能的,因为数据会消失,这是件好事!
更好的解决方案可能是在Text
枚举中也使用Cow
:
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
另一个原因。
通常,您可以像String
一样方便地使用Cow
,因为它们具有许多相同的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();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.