string - 如何在不克隆字符串的情况下在 Rust 中构建灵活的多类型数据系统?
问题描述
我想构建一个系统,其中不同类型的数据(i32
, String
, ...)在修改数据的函数之间流动。例如,我想要一个add
函数来获取“一些”数据并添加它。
该add
函数获取类型的东西,Value
如果Value
是 an i32
,它将两个i32
值相加,如果它是 type String
,则返回一个组合两个字符串的字符串。
我知道这对于模板编程几乎是完美的(或者在 Rust 中调用的任何东西,我来自 C++),但在我的情况下,我想要处理这些东西的小代码块。
例如,使用f64
and String
,使用Float
andText
作为名称,我有:
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()
方法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(),
}
}
}
现在该函数将执行以下操作:
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)
}
在 a 的情况下,Value::Float
this 会创建一个 new String
,然后是一个String
带有结果的 new 并返回它,但在 a 的情况下,Value::Text
this 会克隆字符串,这是一个不必要的步骤,然后创建新的。
有没有一种方法可以让to_string()
实现创建一个新String
的 onValue::Float
但返回Value::Text
' 值的引用?
解决方案
处理 aString
或 a可能性的“标准”方法&str
是使用 a Cow<str>
。COW 代表写时克隆(或写时复制),您可以将其用于字符串以外的其他类型。ACow
允许您保存引用或拥有的值,并且仅在需要对其进行变异时将引用克隆为拥有的值。
有几种方法可以将其应用于您的代码:
- 您可以只添加一个
Into<Cow<str>>
实现并保持其余部分相同。 - 将您的类型更改为始终保持
Cow<str>
,以允许Text
对象保持拥有String
或&str
。
第一个选项是最简单的。您可以只实现该特征。请注意,Into::into
accepts self
,因此您需要为&Value
not实现此功能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>
与数据源联系起来。如果我们只执行 which 是好的,这是不可能的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
now 可以间接持有引用,因此它需要自己的生命周期参数:
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
预期 a 的地方使用,只需传递一个引用。这也是为什么您应该始终尝试&str
在函数参数中接受,而不是String
or的另一个原因&String
。
通常,您可以像使用Cow
s 一样方便地使用String
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();
推荐阅读
- javascript - 用于更改浏览器标签标题的 Javascript 代码不起作用
- python - 我无法使用 python 从给定网站上抓取网络数据
- jenkins - jenkins字符串参数的字符串长度限制是多少
- java - Jsoup 不解析特定的 DIv
- android - Firebase Analytics Log Events
- angular - $event.target.value 对点击事件不起作用
- android - Android Gradle:混合版本会导致运行时崩溃
- javascript - 获取点击 javascript 的动态创建的元素
- mysql - 如何在值组之间添加换行符/标题?
- laravel - Laravel Voyager 路线限制