Reputation: 8078
Suppose I have the following newtype:
pub struct Num(pub i32);
Now, I have a function which accepts an optional Num
:
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):
// 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:
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
?
Motivation: in the program I'm writing, there are several newtypes like Num
, and they wrap i8
, i16
and i32
. And there are many different calc
functions. It's getting very repetitive to write all these match
es at the begining of each calc
function.
Upvotes: 0
Views: 1072
Reputation: 8078
Turns out I figured out a much easier and elegant way to accomplish this. First, implement Default
trait for my newtype:
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:
pub fn calc(nu: Option<Num>) -> i32 {
// do a lot of complicated calculations...
nu.unwrap_or_default().0 * 1234
}
Upvotes: 0
Reputation: 58875
There are two main missing pieces here:
Num
, providing a way to extract the inner value without knowing the outer type.R
to have number-like properties, so that you can express the idea of -1
for it.The first can be solved by implementing Deref
for Num
and then using it as a trait bound. 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:
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
. You will also need to require that R
is Copy
or Clone
so you can move it out of the reference.
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
. 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. But it might be better if the caller only needs to provide T
and let R
be deduced from 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(),
}
}
Upvotes: 1
Reputation: 10464
Such a function would generally be unsafe since the internals might be private (and hence have restricted access). For example, suppose we have a newtype and implement Drop
for it.
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. Then when the struct is dropped, it's able to access invalid 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.
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).
A better API would be to have extract
return an option, rather than some meaningless value or panicking. 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)));
}
Upvotes: 2