ToOwned and AsRef implementation

Hi everybody,

I tried to implement AsRef and ToOwned traits for two structs that hold OsStr/OsString but I can't figure out how to configure lifetimes for it. Could you please tell me is it possible to impl these traits?

Thank you.

pub struct MyStringBuf {
    data: OsString,
    // ... other fields
}
pub struct MyString<'a> {
    data: &'a OsStr,
    // ... other fields
}
impl<'a> AsRef<MyString<'a>> for OsStr {
    fn as_ref(&self) -> &MyString<'a> {
        &MyString { data: self } // lifetime error
    }
}
impl<'a> Borrow<MyString<'a>> for MyStringBuf {
    fn borrow(&self) -> &MyString<'a> {
        &MyString { data: self.data.as_os_str() } // lifetime error
    }
}
impl<'a> ToOwned for MyString<'a> {
    type Owned = MyStringBuf;

    fn to_owned(&self) -> Self::Owned {
        MyStringBuf { data: self.data.to_owned() }
    }
}

this definition is not compatible with the ToOwned and AsRef traits.

for example, the return type of Borrow<Borrowed>::borrow() is &Borrowed, where the elided lifetime is derived from &self, but your type contains a lifetime which is unrelated to &self, so it's not possible to implement Borrow<MyString<'_>> for any type.

if you can make the the wrapper type of OsStr unsized, then it's trivial to implement these, something like:

struct MyString {
    // other fields must come before the unsized `OsStr`
    data: OsStr,
}
1 Like

You can not do that. You can only ever return a reference to something that exists somewhere else, never to something you just created[1].

ToOwned should work as-is.

For the others, you want methods that return an owned MyOsStr<'a> from a &'a OsStr.


  1. The only exception to this if your type looks exactly the same in memory as something that already exists, in which case you can pointer cast your reference to a reference of the other type using unsafe. This is what AsRef<Path> for OsStr does. But that does not seem to apply here, because your type has additional fields. ↩︎

1 Like

On top of what has already been said, you should know about the orphan rules regarding trait implementations. What you attempted to do that cannot be done since you are trying to implement a trait not from your crate for a type not from your crate.

The implementation of the AsRef trait should look like this:

impl AsRef<OsStr> for MyString 
2 Likes

Thank you.

I forgot that one, thank you.

I swapped AsRef impl and it works but I'm not sure how to make ToOwned to work since it requires Borrow.

pub struct MyStringBuf {
    data: OsString,
    // ... other fields
}
pub struct MyString<'a> {
    data: &'a OsStr,
    // ... other fields
}
impl<'a> AsRef<OsStr> for MyString<'a> {
    fn as_ref(&self) -> &OsStr {
        &self.data
    }
}
impl<'a> ToOwned for MyString<'a> {
    type Owned = MyStringBuf; // the trait `Borrow<MyString<'a>>` is not implemented for `MyStringBuf`

    fn to_owned(&self) -> Self::Owned {
        MyStringBuf { data: self.data.to_owned() }
    }
}

In that case you can't implement that trait either, I'm afraid. You can have a method with the same signature, though.

Alternatively, you could try what @nerditation suggested:

...but with that, you'll encounter other issues:

  • custom DSTs are hard[1] to construct without nightly features
  • you can no longer slice your string, because the other data has to be at the start of the referenced part
  • potentially other things I didn't think of.

  1. impossible? ↩︎

No, that's fine from an orphan rule perspective.

https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html#concrete-orphan-rules

1 Like

Ah, interesting. I guess it's because AsRef receives a generic parameter. I replaced Local with () and then I got it to complain about orphan rules.

1 Like

The orphan rules care about generic impls mainly, so if you have a non-generic impl with an uncovered local type as implementor or a trait parameter, you're good.[1]


  1. overlap check might bite, depending on upstream's generic impls ↩︎

2 Likes

Thank you.

I didn't know that, thank you. Unfortunately I have multiple fields in the struct and they would all need to be the last field ;-).

Unfortunately the following code won't work. I'll try if I can implement From/Into instead of AsRef.

pub struct MyStringBuf {
    data: OsString,
    other: String,
}
pub struct MyString<'a> {
    data: &'a OsStr,
    other: &'a str,
}
impl<'a> AsRef<OsStr> for MyString<'a> {
    fn as_ref(&self) -> &OsStr {
        &self.data
    }
}
impl<'a> AsRef<str> for MyString<'a> {
    fn as_ref(&self) -> &str {
        &self.other
    }
}


fn my_function<'a, T: AsRef<MyString<'a>>>(s: T) -> &'a str {
    s.as_ref().other // do something with MyString
}


fn main() {
    let mut a = my_function("abc"); // the trait not satisfied
}

I realized now that this won't work even if I create my own AsRef:

pub struct MyStringBuf {
    data: OsString,
    other: String,
}
pub struct MyString<'a> {
    data: &'a OsStr,
    other: &'a str,
}
trait CustomAsRef<'a, T>
where
    T: ?Sized,
{
    fn as_ref_custom(&'a self) -> &'a T;
}
trait CustomAsRef2<'a, T>
where
    T: ?Sized,
{
    fn as_ref_custom2(&'a self) -> T;
}

impl<'a> CustomAsRef<'a, OsStr> for MyString<'a> {
    fn as_ref_custom(&self) -> &OsStr {
        &self.data
    }
}
impl<'a> CustomAsRef<'a, str> for MyString<'a> {
    fn as_ref_custom(&self) -> &str {
        &self.other
    }
}
impl<'a> CustomAsRef2<'a, MyString<'a>> for OsStr {
    fn as_ref_custom2(&'a self) -> MyString<'a> {
        MyString { data: self, other: "" }
    }
}
impl<'a> CustomAsRef2<'a, MyString<'a>> for str {
    fn as_ref_custom2(&'a self) -> MyString<'a> {
        MyString { data: OsStr::new(""), other: self }
    }
}

fn my_function<'a, T: CustomAsRef2<'a, MyString<'a>>>(s: T) -> MyString<'a> {
    let mut x = s.as_ref_custom2(); // error
    x.data = OsStr::new("foobar");
    x
}

I made it work!

pub struct MyStringBuf {
    data: OsString,
    other: String,
}
pub struct MyString<'a> {
    data: &'a OsStr,
    other: &'a str,
}
trait CustomAsRef<'a, T>
where
    T: ?Sized,
{
    fn as_ref_custom(&'a self) -> &'a T;
}
trait CustomAsRef2<T>
where
    T: ?Sized,
{
    fn as_ref_custom2(self) -> T;
}

impl<'a> CustomAsRef<'a, OsStr> for MyString<'a> {
    fn as_ref_custom(&self) -> &OsStr {
        &self.data
    }
}
impl<'a> CustomAsRef<'a, str> for MyString<'a> {
    fn as_ref_custom(&self) -> &str {
        &self.other
    }
}
impl<'a> CustomAsRef2<MyString<'a>> for &'a OsStr {
    fn as_ref_custom2(self) -> MyString<'a> {
        MyString {
            data: self,
            other: ""
        }
    }
}
impl<'a> CustomAsRef2<MyString<'a>> for &'a str {
    fn as_ref_custom2(self) -> MyString<'a> {
        MyString {
            data: OsStr::new(""),
            other: self,
        }
    }
}

fn my_function<'a, T: CustomAsRef2<MyString<'a>>>(s: T) -> MyString<'a> {
    let mut x = s.as_ref_custom2();
    x.data = OsStr::new("foobar");
    x
}

fn main() {
    let a = my_function("abc");
}

the ?Sized is irrelevant, because T is used as return type. and this trait is basically Into.

1 Like

Yes, you're right :wink: . I've just implemented Into without any problems.

pub struct MyStringBuf {
    data: OsString,
    other: String,
}
pub struct MyString<'a> {
    data: &'a OsStr,
    other: &'a str,
}
impl<'a> AsRef<OsStr> for MyString<'a> {
    fn as_ref(&self) -> &OsStr {
        &self.data
    }
}
impl<'a> AsRef<str> for MyString<'a> {
    fn as_ref(&self) -> &str {
        &self.other
    }
}
impl<'a> Into<MyString<'a>> for &'a OsStr {
    fn into(self) -> MyString<'a> {
        MyString { data: self, other: "" }
    }
}
impl<'a> Into<MyString<'a>> for &'a str {
    fn into(self) -> MyString<'a> {
        MyString {
            data: OsStr::new(""),
            other: self,
        }
    }
}

fn my_function<'a, T: Into<MyString<'a>>>(s: T) -> &'a str {
    s.into().other // do something with MyString
}

fn main() {
    let mut a = my_function("abc"); // the trait not satisfied
}

You might benefit from knowing that by implementing the From trait you get the implementation of Into for free.

impl<'a> From<&'a OsStr> for MyString<'a> {
    fn from(os_str: &'a OsStr) -> MyString<'a> {
        MyString { data: os_str, other: "" }
    }
}

impl<'a> From<&'a str> for MyString<'a> {
    fn from(s: &'a str) -> MyString<'a> {
        MyString { data: OsStr::new(""), other: s }
    }
}

This has the benefit of making your API more flexible:

// Now my_function doesn't need to know about MyString in its signature 
fn my_function(s: &str) -> &str {
    MyString::from(s).other // do something with MyString
}
1 Like

Thank you!

Sorry for deviating from the conversation, but today I encountered another case of trait coherence that made me curious.

As per your example, this works:

impl ForeignTrait<LocalType> for ForeignType

But in the following case the orphan rules kicked-in:

impl ForeignTrait for ForeignType<LocalType>

The difference being that it's the implementor what receives the generic parameter, rather than the foreign trait.

Right, the type positions and order they matter for implementing foreign traits are

//                vvvvvv  vvvvvv  vvvvvv      vvvvvv
impl ForeignTrait<T1<..>, T2<..>, T3<..>> for T0<..>

By ordering I mean that the implementing type T0 is always the first position considered, and then T1, T2, and so on down the trait parameters. (When I say things like "come first" below, I'm using this ordering.)

Also, the covered parameters (parameters of other types, .. above) don't matter (keeping in mind that &Tn, Box<Tn>, and so on don't cover Tn due to some type constructors being fundamental).[1]

Alternatively phrased: at least one of T0, T1, T2, or T3 must be a local type [constructor], where Box<Tn> etc are considered local. That's enough for the non-generic case.

// "`T1`" (`LocalType`) is local so this is fine
impl ForeignTrait<LocalType> for ForeignType

// There's only "`T0`" and it's `ForeignType<..>` so this is not fine
impl ForeignTrait for ForeignType<LocalType>

For the generic case: nothing before the first local can be an uncovered type parameter. But covered type parameters are fine.

// The `T` is covered so this is fine
impl<T> From<LocalType> for Vec<T> { /* ... */ }

// The `LocalType` comes first so this is fine
impl<T: AsRef<str>> AsRef<T> for LocalType { /* ... */ }

// The `T` is uncovered and comes before `LocalType` so this is not fine
impl<T: AsRef<str>> AsRef<LocalType> for T { /* ... */ }

  1. The covered parameters don't matter to the orphan rules. They can matter for overlap checks. ↩︎

1 Like

Thanks for the detailed answer!

Minor question: Aren't the comments for the last two examples switched? I got confused by the wording "comes first" and "comes before".

1 Like

That's the ordering which I guess I wasn't clear enough about. The implementing type is always "first", and then the trait parameters, from left to right. I'll edit my most.

1 Like