[英]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
这样的新类型,它们包装了i8
、 i16
和i32
。 并且有许多不同的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)));
}
这里有两个主要缺失的部分:
Num
的结构,提供一种在不知道外部类型的情况下提取内部值的方法。R
约束为具有类似数字的属性,以便您可以为它表达-1
的想法。 第一个可以通过为Num
实现Deref
然后将其用作特征绑定来解决。 这将让您访问“内部”值。 还有其他具有类似功能的特征,但Deref
可能是您想要的:
第二个可以通过实现从num-traits
crate 导入的One
特征(以获得1
值的概念)并通过实现std::ops::Neg
以能够否定它来解决-1
。 您还需要要求R
是Copy
或Clone
,以便您可以将其移出参考。
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 的具体类型T
和R
,并确保R
是T
's deref target。 但是如果调用者只需要提供T
并让R
从T
推导出来可能会更好。
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.