繁体   English   中英

如何在Rust中构建灵活的多类型数据系统而不克隆字符串?

[英]How to build a flexible multiple type data system in Rust without cloning strings?

我想构建一个系统,在该系统中,不同类型的数据( i32String ,...)在修改数据的函数之间流动。 例如,我想要一个add函数来获取“一些”数据并将其添加。

add函数获得类型为Value东西,如果Valuei32 ,它将两个i32值相加,如果它是String ,则返回一个将两个字符串组合在一起的字符串。

我知道这对于模板编程(或在Rust中所说的任何东西,我来自C ++)来说几乎是完美的,但就我而言,我希望有一些小的代码块来处理这些东西。

例如,使用f64String ,使用FloatText作为名称,我有:

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可以让您持有参考值或拥有值,并且仅在需要对其进行突变时才将参考克隆为拥有值。

您可以通过两种方法将其应用于代码:

  1. 您可以只添加一个Into<Cow<str>>实现,其余的保持不变。
  2. 更改类型以始终保留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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM