7

In rust 1.0.0-nightly, this code works fine:

fn main() {
    let x = 10f64;
    let y = 20f64;
    let z = x + y;
    println!("z = {}", z);
}

But if I try to use a newtype (according to the rust book):

struct Metres(f64);

fn main() {
    let x = Metres(10f64);
    let y = Metres(20f64);
    let z = x + y;
    println!("z = {}", z);
}

I get this compiler error:

test.rs:6:13: 6:18 error: binary operation `+` cannot be applied to type `Metres`
test.rs:6     let z = x + y;
                      ^~~~~
error: aborting due to previous error

Since Metres is basically a f64, why can't the compiler use the same + operator, and create a new Metres object for z?

How can I use newtypes if I can't do simple things like adding and such? How are they "very useful" (as the book calls them)?

(There is an old question about this, but rust changes a lot, hence I'm reasking)

Community
  • 1
  • 1
Amandasaurus
  • 58,203
  • 71
  • 188
  • 248

2 Answers2

11

The reason newtypes work this way is generally because you want to avoid the traits defined on the underlying type. For example, you could have Metres and Feet which both wrap f64 but define addition of Metres and Feet to do unit conversion, which plain f64 addition doesn't give you.

Of course, sometimes you do want the underlying trait implementation. Currently, you'd have to write a wrapper implementation yourself, but there is an RFC for the ability to generate these automatically: https://github.com/rust-lang/rfcs/issues/479

Scott Olson
  • 3,513
  • 24
  • 26
  • That actually makes a lot of sense. It's a shame there's no shorter way to derive things. – Amandasaurus Jan 31 '15 at 14:10
  • 1
    As of Mar. 28, 2016, the crate `derive_more` will do this for many common traits like `Add`, `Sub`, `MulAssign`, etc. https://crates.io/crates/derive_more – U007D Oct 11 '19 at 14:13
5

As mentioned by Scott Olson, it is actually voluntary that newtypes do not "degenerate" in their only attribute. They are here to introduce a new type, after all.

If all you want is a synonym, which is slightly different, then you can use a type alias instead:

type Metres = f64;

However, you then lose the benefits of a new type:

type Grams = f64;

fn main() {
    let m = 10 as Metres;
    let g = 5 as Grams;
    println!("{}", m + g); // prints 15, see http://is.gd/IdYOEg
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722