Why can’t I write TryFrom<AsRef<whatever>>?

I have an API that I want to call that takes C strings. I don’t want to pass in a string which contains a \0 character, and to avoid adding another case to the Result type I’m returning, I want to just protect against that at the type level, so I do something like this:

use std::ffi::OsStr;

struct SpecialStr(OsStr);

impl SpecialStr {
    unsafe fn new_unchecked(source: &OsStr) -> &Self {
        unsafe { &*(source as *const OsStr as *const SpecialStr) }
    }
}

impl<'a, S: AsRef<OsStr>> TryFrom<&'a S> for &'a SpecialStr {
    type Error = ();
    fn try_from(value: &'a S) -> Result<Self, Self::Error> {
        Ok(unsafe { SpecialStr::new_unchecked(value.as_ref()) })
    }
}

But when doing so, I get the error:

error[E0119]: conflicting implementations of trait `TryFrom<&_>` for type `&SpecialStr`
  --> src/lib.rs:18:1
   |
18 | impl<'a, S: AsRef<OsStr>> TryFrom<&'a S> for &'a SpecialStr {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `core`:
           - impl<T, U> TryFrom<U> for T
             where U: Into<T>;

where there is decidedly not an implementation of S.into() -> &SpecialStr for any S (or any type at all).

I’ve also tried the version where I don’t play the same stupid pointer tricks and do this instead:

struct FancyStr<'a>(&'a OsStr);

and it has the same problem.

I know I can just try being less clever, but also I really don’t understand why it seems to be giving me a diagnostic about an impl that clearly does not exist.

(At this point, I am also sorely tempted by the prospect of Just Panic When You Get A String With A Zero Byte In It because what reasonable person would do such a thing?)

Playground link

I’m being informed that std::env::set_var panics when you give it a dumb string so I no longer feel any guilt about doing so.

Also I guess maybe I should have searched the forum before posting, but if anybody has any other enlightening commentary about this I would love to hear more.

The problem that it is considered SemVer compatible, and downstream's "right",[1] to implement:

// This one has nothing to do with your types directly, and they are allowed
// to add it at any time (without causing breakage)
impl AsRef<OsStr> for DownStreamTy { ... }

// This one does involve your type but they still have the right
// (which is why `From<MyType> for Vec<Whatever>` is also allowed, for example)
impl<'a> From<&'a DownStreamTy> for &'a SpecialStr { ... }
// And thus via the blanket implementations in `core`
impl<'a> Into<&'a SpecialStr> for &'a DownStreamTy { ... }
impl<'a> TryFrom<&'a DownStreamTy> for &'a SpecialStr { ... }

That would lead to an overlapping implementation if you were allowed your attempted blanket implementation. So, it throws an error.

The only way to prevent them from exercising this right is to provide the From implementation yourself.[2] More on that shortly. Providing the TryFrom implementation can't "flow backwards" through the blanket implementations to prevent downstream from adding the From implementation.

(I have no idea how possible something like that could be. But I suspect it's simply an untenable constraint satisfaction problem if nothing else.)

I attempted a few wordier explanations, and could try a more technical one, but I think that's as simple as I can outline the error.


So... can you provide the From implementation yourself? You have that "right", but you would have to use a blanket implementation to do so.[3] You should do this the first time your crate is released, as otherwise downstream could add their implementations. In other words: It's a breaking change to add (or expand) a blanket implementation after release, so if you're going to have one, have it from the start.

So you have the right, but can you do so practically? Well, the conversion in your example is unconditional. Was that intentional? If so, you can have

impl<'a, S: ?Sized + AsRef<OsStr>> From<&'a S> for &'a SpecialStr {
    fn from(value: &'a S) -> Self {
        unsafe { SpecialStr::new_unchecked(value.as_ref()) }
    }
}

...so long as SpecialStr doesn't implement AsRef<OsStr>.

This will also give you the TryFrom implementation (with an impossible error type, not ()).

That said, when I've been in similar situations, I just ended up defining my own AsOsStr trait instead, and implementing it for std type that made sense. (In part because not everything in std which could implement AsRef<OsStr> does so, sadly.)

The analogous approach for TryFrom would be to macro up the implementations instead of attempting a blanket implementation, I suppose.


Side note: you should probably

+#[repr(transparent)]
 struct SpecialStr(OsStr);

or &OsStr is allowed to be different than &SpecialStr. Your pointer casts should be fine anyway, but a transmute wouldn't have been sound.


  1. as per the orphan rules ↩︎

  2. or for core to have provided it, in which case you wouldn't be able to either ↩︎

  3. I.e. some generic in the trait parameter ↩︎

I didn’t intend it to look like that; having it unconditionally succeed and using () as an error type was just me being a bit too lazy and thus making it unclear that it was indeed a fallible condition.

Thanks very much for the through explanation—I think I need to read it a couple more times for it to fully sink in but it’s very helpful!