如何在Rust中构建灵活的多类型数据系统而不克隆字符串? [英] How to build a flexible multiple type data system in Rust without cloning strings?
问题描述
我想构建一个系统,在该系统中,不同类型(i32
,String
,...)的数据在修改数据的函数之间流动.例如,我想要一个add
函数来获取一些"数据并将其添加.
I want to build a system where data of different types (i32
, String
, ...) flows between functions that modify the data. For example, I want to have an add
function that gets "some" data and adds it.
add
函数获取类型为Value
的东西,如果Value
是i32
,则将两个i32
值相加,如果它是String
类型,则返回包含以下内容的字符串这两个字符串.
The add
function gets something of type Value
and if Value
is an i32
, it adds the two i32
values, if it is of type String
, it returns a string that combines both strings.
我知道这对于模板编程(或者说Rust中所说的任何东西,我来自C ++)来说几乎是完美的,但是在我的情况下,我希望有小的代码块来处理这些东西.
I know that this would be almost perfect for template programming (or whatever this is called in Rust, I'm coming from C++) but in my case I want to have small code blocks that handle the stuff.
例如,使用Float
和Text
作为名称,使用f64
和String
,我有:
As an example, with f64
and String
, using Float
and Text
as the names, I have:
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()
方法:
Now I want to implement a function that gets a value that is supposed to be a string and does something to it, so I implement the to_string()
method for Value
:
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(),
}
}
}
现在该函数将执行以下操作:
Now the function would do something like:
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
,这将克隆字符串,即不必要的步骤,然后创建新的步骤.
In the case of a Value::Float
this would create a new String
, then a new String
with the result and return it, but in the case of a Value::Text
this would clone the string, which is an unnecessary step, and then create the new one.
to_string()
实现是否可以在Value::Float
上创建新的String
但返回Value::Text
值的引用?
Is there a way where the to_string()
implementation could create a new String
on Value::Float
but return the reference of Value::Text
's value?
推荐答案
处理String
或&str
可能性的标准"方法是使用Cow<str>
. COW代表写时克隆(或 copy -on-write),您可以将其用于字符串以外的其他类型. Cow
允许您保存引用或拥有的值,并且仅在需要对其进行突变时才将引用克隆为拥有的值.
The "standard" way to deal with the possibility of either a String
or a &str
is to use a Cow<str>
. COW stands for clone-on-write (or copy-on-write) and you can use it for other types besides strings. A Cow
lets you hold either a reference or an owned value, and only clone a reference into an owned value when you need to mutate it.
有两种方法可以将其应用于代码:
There are a couple of ways you can apply this to your code:
- 您可以只添加一个
Into<Cow<str>>
实现,其余的保持不变. - 更改类型以始终容纳
Cow<str>
,以允许Text
对象容纳拥有的String
或&str
.
- You can just add an
Into<Cow<str>>
implementation and keep the rest the same. - Change your types to hold
Cow<str>
s throughout, to allowText
objects to hold either an ownedString
or a&str
.
第一个选项最简单.您可以实现特质.请注意,Into::into
接受self
,因此您需要为&Value
而不是Value
实施此操作,否则借入的值将引用into
已使用且已经无效的拥有值.>
The first option is easiest. You can just implement the trait. Note that the Into::into
accepts self
, so you need to implement this for &Value
not Value
, otherwise the borrowed values would be referencing owned values that have been consumed by into
and are already invalid.
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
实现这是不可能的,这很好,因为数据将消失!
Implementing this for &'a Value
lets us tie the lifetime in the Cow<'a, str>
back to the source of the data. This wouldn't be possible if we implemented just for Value
which is good because the data would be gone!
一个更好的解决方案可能是在您的Text
枚举中也使用Cow
:
An even better solution might be to use Cow
in your Text
enum too:
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
现在可以间接保存引用,因此将需要它自己的生命周期参数:
Since Value
now can indirectly hold a reference, it will need a lifetime parameter of its own:
pub enum Value<'a> {
Float(Float),
Text(Text<'a>),
}
现在Into<Cow<str>>
实现可以用于Value
本身,因为可以移动引用的值:
Now the Into<Cow<str>>
implementation can be for Value
itself because referenced values can be moved:
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
或
Just like String
, Cow<str>
satisfies Deref<Target = str>
so it can be used anywhere that a &str
is expected, by just passing a reference. This is another reason why you should always try accept &str
in a function argument, rather than String
or &String
.
通常,您可以像String
一样方便地使用Cow
,因为它们具有许多相同的impl
.例如:
Generally, you can use Cow
s as conveniently as String
s, because they have many of the same impl
s. For example:
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();
这篇关于如何在Rust中构建灵活的多类型数据系统而不克隆字符串?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!