[英]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.