簡體   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