0

I'm trying to create a new type, which will be i16, but print and parse differently:

use std::fmt;
use std::str::FromStr;

pub type Data = i16;

impl fmt::Display for Data {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "0x{:04X}", self)
    }
}

impl FromStr for Data {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let hex: String = s.chars().skip(2).collect();
        let d = Data::from_str_radix(&hex, 16)
            .expect(&format!("Can't parse hex '{}' in '{}'", hex, s));
        Ok(d)
    }
}

#[test]
pub fn prints_itself() {
    let data : Data = 42;
    assert_eq!("0x003A", format!("{}", data));
}

#[test]
pub fn parses_itself() {
    let data : Data = 42;
    assert_eq!(data, "0x003A".parse());
}

It doesn't compile and I think I understand why. It seems I should declare my type Data somehow differently. How?

yegor256
  • 102,010
  • 123
  • 446
  • 597

2 Answers2

4

It does not work because your Data type is just a type alias, not a new type. The type is still an i16, and you cannot implement traits for types you do not own in your crate by design.

What you probably want is to wrap the i16 in a struct as:

pub struct Data(i16);

You will then also need to implement the UpperHex trait in order to print hex, and you'll also probably want to implement the Deref trait to avoid having to type Data.0 to access the i16 every time.

sudo
  • 503
  • 4
  • 12
  • 1
    In the case of implementing the Deref trait, would it look like this? https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bd1c721036ddac62a9144fca69b8f9ea – SeedyROM Mar 02 '22 at 06:30
  • 1
    Yup you got it. Though I think a clearer example of why you might want to do this might look more like: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4da7006a106c8d26b5c0c35b07e3e25e Our function `add` takes references to `i16`s but we are able to pass in references to our `Data` struct thanks to deref coercion. – sudo Mar 02 '22 at 14:25
  • I have no idea how I didn't find this earlier, thanks for the tip. – SeedyROM Mar 03 '22 at 01:00
1

type Foo = Bar doesn't create a new type, it is a "type alias". You can think of it as if the compiler goes and checks for locations of Foo, and replaces them with Bar wherever it is used.

Instead, you want a new type: struct Foo(Bar) and implement any functions/traits you need on the new type.

This is a pretty common pattern, and I have written a library that takes some of the boilerplate out of it, though it is totally optional: https://crates.io/crates/microtype

cameron1024
  • 9,083
  • 2
  • 16
  • 36