String 如何在Rust中构建灵活的多类型数据系统,而无需克隆字符串?
我想构建一个系统,其中不同类型的数据(String 如何在Rust中构建灵活的多类型数据系统,而无需克隆字符串?,string,rust,type-conversion,polymorphism,borrowing,String,Rust,Type Conversion,Polymorphism,Borrowing,我想构建一个系统,其中不同类型的数据(i32,String,…)在修改数据的函数之间流动。例如,我想要一个add函数来获取“一些”数据并将其添加 add函数获取类型为Value的内容,如果Value是一个i32,它将添加两个i32值,如果是String类型,它将返回一个组合两个字符串的字符串 我知道这对于模板编程来说几乎是完美的(或者不管在Rust中叫什么,我来自C++),但是在我的例子中,我希望有一些小的代码块来处理这些东西 例如,使用f64和String,使用Float和Text作为名称,我
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),
}
现在我想实现一个函数,该函数获取一个假定为字符串的值并对其进行处理,因此我实现了值的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
的情况下,这将创建一个新的字符串
,然后创建一个新的字符串
,并返回结果,但在Value::Text
的情况下,这将克隆字符串,这是一个不必要的步骤,然后创建一个新字符串
有没有一种方法可以让to_string()
实现在Value::Float
上创建一个新的string
,但返回Value::Text
的值的引用?处理string
或&str
的可能性的“标准”方法是使用Cow
。COW代表写时克隆(或写时复制),您可以将其用于字符串以外的其他类型。一个Cow
允许您持有一个引用或一个拥有的值,并且只在需要对引用进行变异时将其克隆到拥有的值中
有几种方法可以将其应用于代码:
您只需在
实现中添加一个,其余部分保持不变
将您的类型更改为始终保持Cow
s,以允许Text
对象保持所拥有的字符串或&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),
}
}
}
这将允许您持有借用的&str
:
let string = String::From("hello");
// same as Cow::Borrowed(&string)
let text = Text { value: Cow::from(&string) };
或字符串
:
// same as Cow::Owned(string)
let text = Text { value: Cow::from(string) };
由于Value
现在可以间接保存引用,因此它需要自己的生存期参数:
pub enum Value<'a> {
Float(Float),
Text(Text<'a>),
}
就像String
,Cow
满足Deref
,因此只要传递一个引用,它就可以在任何需要&str
的地方使用。这是另一个原因,而不是String
或&String
通常,您可以像使用String
s一样方便地使用Cow
s,因为它们有许多相同的impl
s。例如:
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();
但是返回引用-Value
的哪个变量允许您返回引用?我的问题是a)我不知道这在一般情况下是否是一个好方法,但即使是,b)to_string()实现必须克隆字符串,如果它已经是一个字符串,这可能是兆字节的数据复制无效。实现到
中,而不是到字符串
。这样,它可以从一个浮点数
返回一个新的字符串
,从一个文本
返回一个&str
。我不明白这一点,非常不清楚,为什么不匹配值
,不使用字符串进行操作呢。字符串非常灵活,但显然不是一种有效的操作方式,这看起来像是一个javascript特性,您希望在Rust中使用。你的意思是“做点什么”功能中的“只匹配”值?这不是一个选项,像这样的函数将有100个,如果不是1000个的话,一旦我引入一个新类型(例如Int),我将不得不全部调整它们。牛的事情听起来不错,直到现在,我是新的生锈,我会看看是否有更好的东西出现,但这是一个选择,我猜。
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,
}
}
}
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();