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();