How to to convert any (most) type to str?

I'm working on an exercise that has the following template code:

pub trait Luhn {
    fn valid_luhn(&self) -> bool;
}

/// Here is the example of how to implement custom Luhn trait
/// for the &str type. Naturally, you can implement this trait
/// by hand for the every other type presented in the test suite,
/// but your solution will fail if a new type is presented.
/// Perhaps there exists a better solution for this problem?
impl<'a> Luhn for &'a str {
    fn valid_luhn(&self) -> bool {
        unimplemented!("Determine if '{}' is a valid credit card number.", self);
    }
}

Tests call the valid_luhn function with different types.

#[test]
fn you_can_validate_from_a_str() {
    assert!("046 454 286".valid_luhn());
    assert!(!"046 454 287".valid_luhn());
}

#[test]
fn you_can_validate_from_a_string() {
    assert!(String::from("046 454 286").valid_luhn());
    assert!(!String::from("046 454 287").valid_luhn());
}

#[test]
fn you_can_validate_from_a_u8() {
    assert!(240u8.valid_luhn());
    assert!(!241u8.valid_luhn());
}

#[test]
fn you_can_validate_from_a_u16() {
    let valid = 64_436u16;
    let invalid = 64_437u16;
    assert!(valid.valid_luhn());
    assert!(!invalid.valid_luhn());
}

I know how to implement valid_luhn for a &str. I figure that if I can somehow convert whatever type into &str, I'd be done. To do that, I'm looking at AsRef, but I'm not clear on what would be the syntax/implementation as the docs only have one trivial example.

Can you help?

For types that are implemented AsRef<str>:

impl<T: AsRef<str>> Luhn for T {
    fn valid_luhn(&self) -> bool {
        unimplemented!("Determine if '{}' is a valid credit card number.", self.as_ref());
    }
}

For types that are implemented Display or ToString:

impl<T: ToString> Luhn for T {
    fn valid_luhn(&self) -> bool {
        unimplemented!("Determine if '{}' is a valid credit card number.", self.to_string());
    }
}

Since numbers are implemented Display instead of AsRef<str>, you can try the second to make tests pass.

1 Like

I expect the better solution is to implement the Luhn trait for the various types. Based just on the usage, I expect that implementing it on a string is based on parsing to an integer; if you start with an integer, there's no need to convert it into a string just to parse it again.

To answer the question in the title, though:

  • ToString uses Display to stringify a type, and is equivalent to format!("{}", it), but can skip the formatting machinery for types that are already strings.
  • AsRef<str> is for types that transparently can be used as strings because they are a string. More commonly you'll see AsRef<Path>; the point is that you can reinterpret e.g. &str as &Path via AsRef.
  • Into<String> is the most generic "I can turn this into a string," but isn't usually implemented for things which aren't a string, which prefer going through an explicit strinigification of Display, since strinigification loses context and can lose information.
3 Likes

numbers are implemented Display instead of AsRef<str>

That's what I needed in this case. In fact, I've done a variation of this exercise previously using From, so, it makes perfect sense now. Thank you.

pub struct Luhn {
    val: String,
}

// Convert input to a String by requiring T implement Display.
impl<T> From<T> for Luhn
where
    T: fmt::Display,
{
    fn from(input: T) -> Self {
        Luhn {
            val: format!("{}", input),
        }
    }
}

impl Luhn {
    pub fn is_valid(&self) -> bool {...}
}

I just now find that types implemented ToString like String and u8 are also implemented Display, why doesen't it cause conflicting implementations like this?

Because the ToString implementation is provided by a blanket implementation in std, roughly

impl<T: Display> ToString for T {
    fn to_string(&self) -> String {
        format!("{}", self)
    }
}

So if you implement Display, you get ToString for free along with it.

1 Like

stdlib also uses unstable specialization to implement optimized fmt-avoiding versions of ToString.

Thus the "roughly" in my answer :slight_smile: (I alluded to this in my first post.)

Though now with fmt::Arguments::as_str, the static specialization may not be necessary anymore... though it'll still help compilation time to skip the formatting machinery, as well as dead code passes seeing that it's unused.

Called out specifically as I believe they were asking about the specialization specifically. There's no conflict in the standard library only because they give themselves access to unstable features like (limited) specialization.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.