0

I need a struct to be treated like an array of 16 unsigned integers, and that passing CreditCard type would be transparent as I would be passing an array of 16 unsigned integers.

How to make this code to work as it was designed to work?

use std::fmt;
/// Credit Card type
#[repr(transparent)]
pub struct CreditCard([u8; 16]);

impl fmt::Display for CreditCard {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}{}{}{}-{}{}{}{}-{}{}{}{}-{}{}{}{}",
            self[0],
            self[1],
            self[2],
            self[3],
            self[4],
            self[5],
            self[6],
            self[7],
            self[8],
            self[9],
            self[10],
            self[11],
            self[12],
            self[13],
            self[14],
            self[15]
        )
    }
}
fn process_cc(card: CreditCard) {
    // do whatever
    println!("processed CC {}", card);
}
fn main() {
    let cc: CreditCard = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4];
    println!("cc = {}", cc);
    let card_data: [u8; 16] = [1, 2, 3, 4, 2, 2, 2, 2, 9, 8, 7, 6, 5, 5, 5, 5];
    process_cc(card_data);
}

Playground

error[E0608]: cannot index into a value of type `&CreditCard`
  --> src/main.rs:11:13
   |
11 |             self[0],
   |             ^^^^^^^

...

error[E0308]: mismatched types
  --> src/main.rs:38:16
   |
38 |     process_cc(card_data);
   |                ^^^^^^^^^ expected struct `CreditCard`, found array of 16 elements
   |
   = note: expected type `CreditCard`
              found type `[u8; 16]`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Nulik
  • 6,748
  • 10
  • 60
  • 129

1 Answers1

5

That's not at all what repr(transparent) is intended for. Frankly, I'm baffled that you found such a niche feature and didn't read the documentation for it:

Structs with this representation have the same layout and ABI as the single non-zero sized field.

This has nothing to do with how the type behaves in the type system, only with how the memory of a value of the type is structured.

What you want to do doesn't even really belong in a strongly typed language. You can't just assign an array to another type because it's another type. With repr(transparent) it's valid to transmute the bits from one to another, but that will never happen automatically.

The better alternative is to implement Deref and From for your type:

use std::ops::Deref;

impl Deref for CreditCard {
    type Target = [u8; 16];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl From<[u8; 16]> for CreditCard {
    fn from(other: [u8; 16]) -> Self {
        CreditCard(other)
    }
}

Then take any type that can be turned into a CreditCard:

fn process_cc(card: impl Into<CreditCard>) {
    // do whatever
    println!("processed CC {}", card.into());
}

See also:


If you were dead-set on using repr(transparent), you'd need to do something like:

fn process_cc(card: [u8; 16]) {
    use std::mem;
    let card: CreditCard = unsafe { mem::transmute(card) };
    // do whatever
    println!("processed CC {}", card);
}

This is generally a very bad idea and it's highly likely you should not do this.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    I did read the docs, but I believed it would work indeed as a single field and not a struct – Nulik Jun 14 '19 at 12:54
  • `Deref` is just perfect in my case. Thanks! – Nulik Jun 14 '19 at 13:06
  • I think it's a fair question. I think the question hints that the OP read the documentation but merely came to some wrong conclusions about the usage. Otherwise, great response. – solartic Sep 10 '19 at 14:53
  • @solartic I also think it's a valid question (which is why I answered it). My opinion is that `repr(transparent)` is something that you are unlikely to stumble on; there's not going to be a bunch of documentation or blog posts talking about it to lead people down the path of thinking thy should use it. To me, this means that you have to go looking for `repr(...)` to start with, presumably by looking to solve a specific problem (not the one indicated by the OP). That's why I have such a surprise that the question was asked in the first place. – Shepmaster Sep 10 '19 at 15:15
  • Looking at it from that perspective I can understand your surprise. Though I was thinking about it from a different perspective. I.E, the OP stumbled across repr(...) in the past (saw it in some source code), and read its documentation and moved on. Only to thinking about it again in the future while trying to solve a problem and thinking it might be a good fit but not fulling understanding its usage. In my case I can see how reading the documentation can lead someone to trying something similar to what the OP did. – solartic Sep 10 '19 at 16:22