简体   繁体   English

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

[英]Newtype as generic parameter in Rust?

Suppose I have the following newtype:假设我有以下新类型:

pub struct Num(pub i32);

Now, I have a function which accepts an optional Num :现在,我有一个接受可选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
}

What I want to write is a generic extract function like the one below (which won't compile):我想写的是一个通用的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
    }
}

So that I can bypass the match inside my calc function:这样我就可以绕过我的 calc function 中的match

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

How can I write extract ?我该如何写extract

Motivation: in the program I'm writing, there are several newtypes like Num , and they wrap i8 , i16 and i32 .动机:在我正在编写的程序中,有几个像Num这样的新类型,它们包装了i8i16i32 And there are many different calc functions.并且有许多不同的calc函数。 It's getting very repetitive to write all these match es at the begining of each calc function.在每个calc function 的开头编写所有这些match es 变得非常重复。

Such a function would generally be unsafe since the internals might be private (and hence have restricted access).这样的 function 通常是不安全的,因为内部可能是私有的(因此访问受限)。 For example, suppose we have a newtype and implement Drop for it.例如,假设我们有一个新类型并为它实现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!(),
    // };
}

(playground) (操场)

If your function worked, you'd be able to move the inner string out of the newtype.如果您的 function 工作正常,您就可以将内弦移出新类型。 Then when the struct is dropped, it's able to access invalid memory.然后当结构被删除时,它能够访问无效的 memory。

Nonetheless, you can write a macro that implements something along those lines.尽管如此,您可以编写一个宏来实现这些方面的某些东西。 If you try to use the macro on something implementing Drop , the compiler will complain, but otherwise, this should work.如果您尝试在实现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)));
}

(playground) (操场)

Having an impl block in the output of the macro doesn't cause any problems since you can have as many impl blocks for a single type as you need (in the original module).在宏的 output 中有一个impl块不会导致任何问题,因为您可以根据需要(在原始模块中)为单一类型拥有尽可能多的impl块。

A better API would be to have extract return an option, rather than some meaningless value or panicking.更好的 API 是让extract返回一个选项,而不是一些无意义的值或恐慌。 Then the any error can easily be handled by the caller.然后调用者可以轻松处理任何错误。

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

(playground) (操场)

There are two main missing pieces here:这里有两个主要缺失的部分:

  1. You need to abstract the structure of Num , providing a way to extract the inner value without knowing the outer type.您需要抽象Num的结构,提供一种在不知道外部类型的情况下提取内部值的方法。
  2. You need to constrain R to have number-like properties, so that you can express the idea of -1 for it.您需要将R约束为具有类似数字的属性,以便您可以为它表达-1的想法。

The first can be solved by implementing Deref for Num and then using it as a trait bound.第一个可以通过为Num实现Deref然后将其用作特征绑定来解决。 This will let you access the "inner" value.这将让您访问“内部”值。 There are also other traits that have similar capabilities, but Deref is likely the one you want here:还有其他具有类似功能的特征,但Deref可能是您想要的:

The second can be solved by implementing the One trait imported from the num-traits crate (to get the idea of a 1 value) and by implementing std::ops::Neg to be able to negate it to get -1 .第二个可以通过实现从num-traits crate 导入的One特征(以获得1值的概念)并通过实现std::ops::Neg以能够否定它来解决-1 You will also need to require that R is Copy or Clone so you can move it out of the reference.您还需要要求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(),
    }
}

Depending on how you intend to use this, you might want to get rid of R , since it is always determined by T .根据您打算如何使用它,您可能希望摆脱R ,因为它始终由T确定。 As-is, the function is told by the caller the concrete types of T and R , and will make sure that R is T 's deref target.照原样,调用者告诉 function 的具体类型TR ,并确保RT 's deref target。 But it might be better if the caller only needs to provide T and let R be deduced from T .但是如果调用者只需要提供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(),
    }
}

Turns out I figured out a much easier and elegant way to accomplish this.事实证明,我想出了一个更简单、更优雅的方法来完成这个。 First, implement Default trait for my newtype:首先,为我的新类型实现Default特征:

use std::default::Default;

pub struct Num(pub i32);

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

And then, when needed, just use unwrap_or_default accessing first newtype tuple element:然后,在需要时,只需使用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