繁体   English   中英

新类型作为 Rust 中的通用参数?

[英]Newtype as generic parameter in Rust?

假设我有以下新类型:

pub struct Num(pub i32);

现在,我有一个接受可选Num的 function :

pub fn calc(nu: Option<Num>) -> i32 {
    let real_nu = match nu { // extract the value inside Num
        Some(nu) => nu.0,
        None     => -1
    };
    // performs lots of complicated calculations...
    real_nu * 1234
}

我想写的是一个通用的extract function ,如下所示(不会编译):

// T here would be "Num" newtype
// R would be "i32", which is wrapped by "Num"

pub fn extract<T, R>(val: Option<T>) -> R {
    match val {
        Some(val) => val.0, // return inner number
        None      => -1 as R
    }
}

这样我就可以绕过我的 calc function 中的match

pub fn calc(nu: Option<Num>) -> i32 {
    // do a lot of complicated calculations...
    extract(nu) * 1234 // automatically extract i32 or -1
}

我该如何写extract

动机:在我正在编写的程序中,有几个像Num这样的新类型,它们包装了i8i16i32 并且有许多不同的calc函数。 在每个calc function 的开头编写所有这些match es 变得非常重复。

这样的 function 通常是不安全的,因为内部可能是私有的(因此访问受限)。 例如,假设我们有一个新类型并为它实现Drop

struct NewType(String);

impl Drop for NewType {
    fn drop(&mut self) {
        println!("{}", self.0)
    }
}

fn main() {
    let x = NewType("abc".to_string());
    let y = Some(x);

    // this causes a compiler error
    // let s = match y {
    //     Some(s) => s.0,
    //     None => panic!(),
    // };
}

(操场)

如果您的 function 工作正常,您就可以将内弦移出新类型。 然后当结构被删除时,它能够访问无效的 memory。

尽管如此,您可以编写一个宏来实现这些方面的某些东西。 如果您尝试在实现Drop的东西上使用宏,编译器会抱怨,否则,这应该可以工作。

macro_rules! extract_impl {
    (struct $struct_name: ident($type_name: ty);) => {
        struct $struct_name($type_name);
        impl $struct_name {
            fn extract(item: Option<Self>) -> $type_name {
                match item {
                    Some(item) => item.0,
                    None => panic!(), // not sure what you want here
                }
            }
        }
    };
}

extract_impl! {
    struct Num(i32);
}

impl Num {
    fn other_fun(&self) {}
}

fn main() {
    let x = Num(5);
    println!("{}", Num::extract(Some(x)));
}

(操场)

在宏的 output 中有一个impl块不会导致任何问题,因为您可以根据需要(在原始模块中)为单一类型拥有尽可能多的impl块。

更好的 API 是让extract返回一个选项,而不是一些无意义的值或恐慌。 然后调用者可以轻松处理任何错误。

macro_rules! extract_impl {
    (struct $struct_name: ident($type_name: ty);) => {
        struct $struct_name($type_name);
        impl $struct_name {
            fn extract(item: Option<Self>) -> Option<$type_name> {
                item.map(|item| item.0)
            }
        }
    };
}

extract_impl! {
    struct Num(i32);
}

impl Num {
    fn other_fun(&self) {}
}

fn main() {
    let x = Num(5);
    println!("{:?}", Num::extract(Some(x)));
}

(操场)

这里有两个主要缺失的部分:

  1. 您需要抽象Num的结构,提供一种在不知道外部类型的情况下提取内部值的方法。
  2. 您需要将R约束为具有类似数字的属性,以便您可以为它表达-1的想法。

第一个可以通过为Num实现Deref然后将其用作特征绑定来解决。 这将让您访问“内部”值。 还有其他具有类似功能的特征,但Deref可能是您想要的:

第二个可以通过实现从num-traits crate 导入的One特征(以获得1值的概念)并通过实现std::ops::Neg以能够否定它来解决-1 您还需要要求RCopyClone ,以便您可以将其移出参考。

use num_traits::One;
use std::ops::{Deref, Neg}; // 0.2.8

pub struct Num(pub i32);

impl Deref for Num {
    type Target = i32;
    fn deref(&self) -> &i32 {
        &self.0
    }
}

pub fn extract<T, R>(val: Option<T>) -> R
where
    T: Deref<Target = R>,
    R: Neg<Output = R> + One + Copy,
{
    match val {
        Some(val) => *val,
        None => -R::one(),
    }
}

根据您打算如何使用它,您可能希望摆脱R ,因为它始终由T确定。 照原样,调用者告诉 function 的具体类型TR ,并确保RT 's deref target。 但是如果调用者只需要提供T并让RT推导出来可能会更好。

pub fn extract<T>(val: Option<T>) -> T::Target
where
    T: Deref,
    <T as Deref>::Target: Neg<Output = T::Target> + One + Copy,
{
    match val {
        Some(val) => *val,
        None => -T::Target::one(),
    }
}

事实证明,我想出了一个更简单、更优雅的方法来完成这个。 首先,为我的新类型实现Default特征:

use std::default::Default;

pub struct Num(pub i32);

impl Default for Num {
    fn default() -> Self {
        Self(-1)
    }
}

然后,在需要时,只需使用unwrap_or_default访问第一个 newtype 元组元素:

pub fn calc(nu: Option<Num>) -> i32 {
    // do a lot of complicated calculations...
    nu.unwrap_or_default().0 * 1234
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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