如何在Rust中构建灵活的多类型数据系统而不克隆字符串? [英] How to build a flexible multiple type data system in Rust without cloning strings?

查看:127
本文介绍了如何在Rust中构建灵活的多类型数据系统而不克隆字符串?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想构建一个系统,在该系统中,不同类型(i32String,...)的数据在修改数据的函数之间流动.例如,我想要一个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的东西,如果Valuei32,则将两个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.

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

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:

  1. 您可以只添加一个Into<Cow<str>>实现,其余的保持不变.
  2. 更改类型以始终容纳Cow<str>,以允许Text对象容纳拥有的String&str.
  1. You can just add an Into<Cow<str>> implementation and keep the rest the same.
  2. Change your types to hold Cow<str>s throughout, to allow Text objects to hold either an owned String 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 Cows as conveniently as Strings, because they have many of the same impls. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆