首页 > 解决方案 > 新类型作为 Rust 中的泛型参数?

问题描述

假设我有以下新类型:

pub struct Num(pub i32);

现在,我有一个接受可选的函数Num

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像下面这样的通用函数(不会编译):

// 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
    }
}

这样我就可以绕过match我的 calc 函数内部:

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

我该怎么写extract

动机:在我正在编写的程序中,有几个新类型,例如,并且Num它们 wrapi8和. 并且有许多不同的功能。在每个函数的开头编写所有这些 es变得非常重复。i16i32calcmatchcalc

标签: genericsrust

解决方案


这样的函数通常是不安全的,因为内部可能是私有的(因此访问受限)。例如,假设我们有一个新类型并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!(),
    // };
}

(操场)

如果您的函数有效,您将能够将内部字符串移出新类型。然后当结构被删除时,它能够访问无效内存。

尽管如此,您可以编写一个宏来实现这些方面的某些东西。如果您尝试在实现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)));
}

(操场)

在宏的输出中包含一个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)));
}

(操场)


推荐阅读