31

In Rust, tuple structs with only one field can be created like the following:

struct Centimeters(i32);

I want to do basic arithmetic with Centimeters without extracting their "inner" values every time with pattern matching, and without implementing the Add, Sub, ... traits and overloading operators.

What I want to do is:

let a = Centimeters(100);
let b = Centimeters(200);
assert_eq!(a + a, b);
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366

3 Answers3

34

is there a way to do it without extracting their "inner" values every time with pattern matching, and without implementing the Add, Sub, ... traits and overloading operators?

No, the only way is to implement the traits manually. Rust doesn't have an equivalent to the Haskell's GHC extension GeneralizedNewtypeDeriving which allows deriving on wrapper types to automatically implement any type class/trait that the wrapped type implements (and with the current set-up of Rust's #[derive] as a simple AST transformation, implementing it like Haskell is essentially impossible.)

To abbreviate the process, you could use a macro:

use std::ops::{Add, Sub};

macro_rules! obvious_impl {
    (impl $trait_: ident for $type_: ident { fn $method: ident }) => {
        impl $trait_<$type_> for $type_ {
            type Output = $type_;

            fn $method(self, $type_(b): $type_) -> $type_ {
                let $type_(a) = self;
                $type_(a.$method(&b))
            }
        }
    }
}

#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
pub struct Centimeters(i32);

obvious_impl! { impl Add for Centimeters { fn add } }
obvious_impl! { impl Sub for Centimeters { fn sub } }

#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
pub struct Inches(i32);

obvious_impl! { impl Add for Inches { fn add } }
obvious_impl! { impl Sub for Inches { fn sub } }

fn main() {
    let a = Centimeters(100);
    let b = Centimeters(200);
    let c = Inches(10);
    let d = Inches(20);
    println!("{:?} {:?}", a + b, c + d); // Centimeters(300) Inches(30)
    // error:
    // a + c;
}

playpen

I emulated the normal impl syntax in the macro to make it obvious what is happening just by looking at the macro invocation (i.e. reducing the need to look at the macro definition), and also to maintain Rust's natural searchability: if you're looking for traits on Centimeters just grep for for Centimeters and you'll find these macro invocations along with the normal impls.

If you are accessing the contents of the Centimeters type a lot, you could consider using a proper struct with a field to define the wrapper:

struct Centimeters { amt: i32 }

This allows you to write self.amt instead of having to do the pattern matching. You can also define a function like fn cm(x: i32) -> Centimeters { Centimeters { amt: x } }, called like cm(100), to avoid the verbosity of constructing a full struct.

You can also access the inner values of a tuple struct using the .0, .1 syntax.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
huon
  • 94,605
  • 21
  • 231
  • 225
  • 2
    I was almost sure that macros were the way to go, but I have not mastered them yet :) If you suggest proper structs then what exactly the use case is for one-field-structs? – anonymous_user_13 Jul 19 '14 at 06:12
  • 2
    @anonymous_user_13 you mean a one-field tuple struct? The default construction syntax is nicer and not having to think of a name for the field are the the only benefits I can think of. – huon Jul 19 '14 at 06:22
29

I made the derive_more crate for this problem. It can derive lots of traits for structs of which the elements implement them.

You need to add derive_more to your Cargo.toml. Then you can write:

#[macro_use]
extern crate derive_more;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Add)]
struct Centimeters(i32);

fn main() {
    let a = Centimeters(100);
    let b = Centimeters(200);
    assert_eq!(a + a, b);
}
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
JelteF
  • 3,021
  • 2
  • 27
  • 35
-3

For Rust version 1.10.0, it seems to me that type aliases would perfectly fit the situation you are describing. They simply give a type a different name.

Let's say all centimeters are u32s. Then I could just use the code

type Centimeters = u32;

Any trait that u32 has, Centimeters would automatically have. This doesn't eliminate the possibility of adding Centimeters to Inches. If you're careful, you wouldn't need different types.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
5hammer
  • 69
  • 6
  • 9
    *If you're careful, you wouldn't need different types.* — If you're careful enough you don't need a language like Rust ^_^. Types are supposed to help the programmer. – Shepmaster Sep 10 '16 at 02:10
  • agreed just offering a potentional idea – 5hammer Sep 12 '16 at 00:33
  • 1
    D went further down this route by introducing both _type aliases_ (like above) and _new types_ (similar, but no implicit conversions). – dhardy Oct 21 '16 at 12:22