当在Rust中转换失败时,可以有选择地规范化值的惯用方式是什么?

时间:2019-06-14 19:01:53

标签: rust

我希望定义一个检查两个值并有选择地将其中一个更改为匹配类型的函数。目的是对数学公式执行自动转换。我的规则是:

  • 两个人都是i64,别说了
  • 两个人都是f64,别说了
  • 一个是i64,一个是f64,将i64更改为f64
  • 其他类型的其他规则

例如:

fn normalize(arg1: Option<MyValue>, arg2: Option<MyValue>) 
    -> (Option<MyValue>, Option<MyValue>) {
  ... do stuff...
}

我的示例将返回带有可选转换值的元组。 MyValue未实现Copy,但确实实现了Clone。它是一个枚举,可以容纳整数,有理数或字符串等。

我可以看到的替代方法是:

  1. 返回一个元组。转换需要更改的值,克隆另一个以避免借用检查器。转换失败返回为None
  2. 返回一个元组。转换需要更改的值,返回另一个不变的值,找出如何移动值。
  3. 返回()。设置参数&mut。更改需要更改的内容。
  4. 我不知道的其他方式,因为我是Rust的新手。

哪种方法最适合Rust?如果我不克隆,该如何标记签名以使借用检查器更安心?

我真正的枚举是:

#[derive(Clone, PartialEq, Debug)]
pub enum ShyScalar {
    Boolean(bool),
    Integer(i64),
    Rational(f64),
    String(String),
    Error(String)
}

1 个答案:

答案 0 :(得分:0)

您可以将具有关联类型的特征视为在类型之间进行映射的编译时函数。例如:

trait MapType {
    type Output;
}

impl MapType for f64 {
    type Output i64;
}

impl MapType for bool {
    type Output u8;
}

对于您可能需要的每种类型,可以实现MapType以提供到唯一Output类型的映射。

您的情况是关于类型对的,您可以通过添加参数来扩展上面的想法:

trait Normalize<T>: Sized {
    type Norm;
}

Normalize的每个实现都会产生唯一的Norm类型,并将SelfT两种类型组合在一起。

但是您还需要一些约束;毕竟,您将需要能够在这些类型之间进行实际转换。而且,如果数字太大而无法转换,则转换将失败,因此您将需要额外的类型约束TryFromTryInto,以表示可以将哪些内容转换为以下内容:

use std::convert::{TryFrom, TryInto};

trait Normalize<T>: Sized
where
    T: TryInto<Self::Norm>,
{
    type Norm: TryFrom<Self>;
}

为您想要的类型对实现它:

impl Normalize<u32> for f64 {
    type Norm = f64;
}

impl Normalize<f64> for u32 {
    type Norm = f64;
}

并且对于所有成对的 类型:

impl<X> Normalize<X> for X {
    type Norm = X;
}

然后您可以像这样实现normalize

fn normalize<A, B>(arg1: Option<A>, arg2: Option<B>) -> (Option<A::Norm>, Option<A::Norm>) 
where
    A: Normalize<B>,
    A::Norm: TryFrom<B>,
{
    (
        arg1.and_then(|a| a.try_into().ok()),
        arg2.and_then(|b| b.try_into().ok())
    )
}

fn main() {
    println!("{:?}", normalize(Some(1u32), Some(1u32))); // (Some(1), Some(1))
    println!("{:?}", normalize(Some(1f64), Some(1u32))); // (Some(1.0), Some(1.0))
    println!("{:?}", normalize(Some(1u32), Some(1f64))); // (Some(1.0), Some(1.0))
    println!("{:?}", normalize(Some(1f64), Some(1f64))); // (Some(1.0), Some(1.0))
}

通过使用u32而不是u64,您会很快意识到我在这里有点作弊。这是因为there is no TryFrom<u64> implementation for f64。如果您需要这些类型对,则仍然可以使用我概述的方法,但是您需要定义自己的TryIntoTryFrom特征版本,并为所有输入所需的配对。