[英]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
这样的新类型,它们包装了i8
、 i16
和i32
。 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!(),
// };
}
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)));
}
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)));
}
There are two main missing pieces here:这里有两个主要缺失的部分:
Num
, providing a way to extract the inner value without knowing the outer type.Num
的结构,提供一种在不知道外部类型的情况下提取内部值的方法。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.您还需要要求
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(),
}
}
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 的具体类型
T
和R
,并确保R
是T
's deref target。 But it might be better if the caller only needs to provide T
and let R
be deduced from T
.但是如果调用者只需要提供
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(),
}
}
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.