Looking for trait combining behavior of AsRef and ToOwned

This is an attempt at getting the best of both world, I might be looking at this the wrong way.

Whenever a function needs both a ref and and owned value of T, i'd like to declare the parameter as t: impl AsMagic<T> so that I may use t.as_ref() and t.to_owned(), however without creating unneeded copies when an owned value is available, and avoids unsafe.

Ie:

  • When the function is passed a &T
    • and needs a &T, return self
    • and needs a T, return self.to_owned()
  • When the function is passed a T
    • and needs a &T, return &self
    • and need a T, return self

The following works but isnt free because is uses an enum and isnt ergonomic because .into() needs to be called when passing parameters.


enum OwnedRef<'a, T> {
    Owned(T),
    Ref(&'a T),
}

impl<'a, T: ToOwned<Owned = T>> OwnedRef<'a, T> {
    fn as_ref(&self) -> &T {
        match self {
            OwnedRef::Owned(t) => &t,
            OwnedRef::Ref(t) => t,
        }
    }
    fn to_owned(self) -> T {
        match self {
            OwnedRef::Owned(t) => t,
            OwnedRef::Ref(t) => ToOwned::to_owned(t),
        }
    }
}

impl<'a, T> From<&'a T> for OwnedRef<'a, T> {
    fn from(value: &'a T) -> Self {
        Self::Ref(value)
    }
}

impl<'a, T> From<T> for OwnedRef<'a, T> {
    fn from(value: T) -> Self {
        Self::Owned(value)
    }
}

Usage

fn helper<'a, K>(k: OwnedRef<'a, K>) {
    k.as_ref();
    k.to_owned();
}

helper(String::new().into())

That looks extremely similar to Cow in std::borrow - Rust -- perhaps it can inspire you somehow?

4 Likes

It very much is however I'm looking more for "Mow" Move on write.

Edit:
I'm dumb,

to_mut will obtain a mutable reference to an owned value, cloning if necessary.

It`s almost exactly what I need, Thank you!

Is there any way to enforce at-most-once call to to_mut?

I wrote GenericCow to solve this with zero-cost, where possible. (Opposed to Cow, which always works with an enum at runtime.)

I wasn't able to solve this ergonomically either, as I have to wrap values in Owned.

2 Likes

I don't know if there's a way to be as general as you're aiming for and also ergonomic (note the need for turbofish due to ambiguity).

1 Like

Note that this is simply not possible if you really meant what is described as-is. If you pass something by value to a function, you can't return a reference to it, that'd be a dangling reference or a use-after-free.

Note that the counterpart of to_owned is borrow and not as_ref. Getting the reference from a Cow, for example, can be achieved through dereference or borrow. While Cow also lets you obtain a reference using as_ref[1], this is (arguably) semantically wrong, see Semantics of AsRef. Also see this note in the AsRef documentation:

[…] many smart pointers provide an as_ref implementation which simply returns a reference to the pointed-to value (but do not perform a cheap reference-to-reference conversion for that value). However, AsRef::as_ref should not be used for the sole purpose of dereferencing; instead Deref coercion’ can be used:

let x = Box::new(5i32);
// Avoid this:
// let y: &i32 = x.as_ref();
// Better just write:
let y: &i32 = &x;

Moreover, ToOwned::to_owned does not consume the receiver, so it cannot achieve the goal of:

(At least not without an unnecessary clone.)

There is a method into_owned, which consumes self, but that's an inherent method of Cow and not part of any trait.

So what you are looking for is neither AsRef nor ToOwned. Instead you'd need a trait which combines Borrow with something like Cow::into_owned, which, opposed to to_owned, consumes self.

This is exactly what GenericCow (as mentioned above) does (with the addition of demanding a Deref implementation, see rationale).


  1. See also PR #28811 which introduced that for several smart pointers. ↩︎

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.