Smart pointer which owns its target

Trying to understand this now. I assume that "by-ref" you mean either .as_ref(), .deref(), or .borrow() without specifying it.

use std::borrow::Borrow;
use std::ops::Deref;

fn main() {
    let s: String = "".to_string();
    let _: &str = s.as_ref();
    let _: &str = s.deref();
    let _: &str = s.borrow();
}

(Playground)

All three work. (It's a bit confusing that .as_ref() can do the same as .deref() :sweat_smile:.)

I would agree.

I also agree.

Hmmm… :thinking: by definition, I said IntoOwned was a pointer-like type. This rules out the third and fourth concept:

Maybe I should have named it PointerIntoOwned, which I did in one of my earlier approaches, instead of IntoOwned. The name "IntoOwned" is misleading: it's not an arbitrary type that can be converted into an "owned" type (whatever that is), but it is

  • a pointer-like type, which
  • can be converted into an "owned" type (IntoOwned::Owned), that
    • can be used "by-ref" (through .borrow()) as the pointed-to type.

So IntoOwned isn't a general conversion into an owned type but a bit more specific.

Speaking of naming …

… if Owned is not an "owned" type but a smart pointer that owns its pointee, perhaps it should be better named Owning instead of Owned?

But back to this:

Not sure why you think I use the fourth concept. Maybe because of the following implementations?

impl<T: ?Sized> IntoOwned for Box<T> {
    type Owned = Self;
    fn into_owned(self) -> Self::Owned {
        self
    }
}

impl<T> IntoOwned for Vec<T> {
    type Owned = Self;
    fn into_owned(self) -> Self::Owned {
        self
    }
}

impl IntoOwned for String {
    type Owned = Self;
    fn into_owned(self) -> Self::Owned {
        self
    }
}

(source)

This looks a bit like IntoOwned supports .into_owned() for some types but not for others. But why is that? It's because IntoOwned actually is more like PointerLikeTypeThatCanBeConvertedIntoAnOwnedTypeFromWhichYouCanBorrowTheOriginalPointee. And this shouldn't (and can't) be implemented by i32, for example.

I think that:

  • IntoOwned as defined in version 0.3.0 of deref_owned is a generalization of std::borrow::ToOwned (as suggested in this post); i.e. if we had IntoOwned we wouldn't need ToOwned (given issue #20671 was fixed and assuming we'd be happy with the different syntax when using the trait).
  • A "smart pointer which owns its target" (whether we call it Owned or Owning) is necessary if we want to return a pointer-like type to a value where sometimes the value is owned and sometimes it's not, and where this is determined at compile-time through a GAT. (For the run-time case, we already have Cow, which works fine.)

Maybe it's possible to find an even more general version of IntoOwned like you suggested (maybe if Rust's type system is more advanced in future), and that might be a good reason to not add such a trait to std now.

One thing that I don't like about my version of IntoOwned is that apparently all smart pointers have to implement it (in addition to Deref), and most often that implementation is trivial:

impl<T> IntoOwned for SomeSmartPointer<T> {
    type Owned = Self;
    fn into_owned(self) -> Self::Owned {
        self
    }
}

I try to understand better how &'a T, Cow<'a, T>, and Owned<T> seem to be the only examples that have different implementations. This makes me suspect that something might be still wrong.

&'a T and Cow<'a, T> are having a lifetime, so that justifies a different implementation (to get rid of it during the process). But what about Owned<T>? We might try to write:

impl<T> IntoOwned for Owned<T> {
    type Owned = Self;
    fn into_owned(self) -> Self::Owned {
        self
    }
}

I.e. why is Owned<T> not converted to Owned<T> when going through .into_owned()? I get this interesting error message when I try:

error[E0277]: the trait bound `T: Clone` is not satisfied
   --> src/lib.rs:119:18
    |
119 |     type Owned = Self;
    |                  ^^^^ the trait `Clone` is not implemented for `T`
    |
    = note: required because of the requirements on the impl of `ToOwned` for `T`
note: required because of the requirements on the impl of `Borrow<T>` for `Owned<T>`
   --> src/lib.rs:59:9
    |
59  | impl<B> Borrow<B> for Owned<<B as ToOwned>::Owned>
    |         ^^^^^^^^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: required by a bound in `IntoOwned::Owned`
   --> src/lib.rs:93:17
    |
93  |     type Owned: Borrow<<Self as Deref>::Target>;
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `IntoOwned::Owned`
help: consider restricting type parameter `T`
    |
118 | impl<T: std::clone::Clone> IntoOwned for Owned<T> {
    |       +++++++++++++++++++

But we can fix that with:

impl<B> Borrow<B> for Owned<B> {
    fn borrow(&self) -> &B {
        &self.0
    }
}

impl<B> BorrowMut<B> for Owned<B> {
    fn borrow_mut(&mut self) -> &mut B {
        &mut self.0
    }
}

Now these implementations are all the same:

impl<T> IntoOwned for Owned<T> {
    type Owned = Self;
    fn into_owned(self) -> Self::Owned {
        self
    }
}

impl<T: ?Sized> IntoOwned for Box<T> {
    /* … same here … */
}

impl<T> IntoOwned for Vec<T> {
    /* … same here … */
}

impl IntoOwned for String {
    /* … same here … */
}

(and we could add even more for Rc, Arc, etc.)

But now IntoOwned won't allow me to go from Owned<i32> to i32 anymore. With &'a T and Cow<'a, T> everything worked fine though. So this isn't really an improvement.

Maybe the correct way would be to simply remove the implementations of IntoOwned for Box<T>, Vec<T>, String, etc. Where needed, a smart pointer to the value pointed-to by an owned inner value (:face_with_spiral_eyes:) could be used. This is what I previously did in mmtkvdb when I wanted to return a re-aligned array of i32's for example.

Perhaps this change would make things a bit less messy or a bit less arbitary. IntoOwned would then only be implemented for &'a T, Cow<'a, T>, and Owned<T>. I'll look into that.


Update:

Maybe the correct way would be to simply remove the implementations of IntoOwned for Box<T>, Vec<T>, String, etc.

Done in deref_owned version 0.4.0.