简体   繁体   中英

Why does the Borrow trait require the borrowed type to be a reference?

Imagine some event source, which produces events represented as an enum. Of course, for best efficiency, this producer is zero-copy, ie it returns references to its internal buffers:

enum Variant<'a> {
    Nothing,
    SomeInt(u64),
    SomeBytes(&'a [u8])
}

impl Producer {
    fn next(&'a mut self) -> Variant<'a> { ... }
}

This is perfectly fine for consumers that don't require lookahead or backtracking, but sometimes there is a need to save some sequence of events. Thus, our Variant type becomes a generic:

enum Variant<BytesT> {
    Nothing,
    SomeInt(u64),
    SomeBytes(BytesT)
}

type OwnedVariant = Variant<Vec<u8>>;
type BorrowedVariant<'a> = Variant<&'a [u8]>;

Here, we end up with two types with "owner-reference" relationship, which is similar to pairs Vec<T> - &[T] , String - &str . Docs suggest builtin traits Borrow and ToOwned which provide just what is required except for a subtle nuance:

trait Borrow<Borrowed: ?Sized> {
    fn borrow(&self) -> &Borrowed;
    // this: -----------^
}

pub trait ToOwned {
    type Owned: Borrow<Self>;
    fn to_owned(&self) -> Self::Owned;
}

Result of borrow is required to be a reference to something , which BorrowedVariant<'a> is obviously not. Removing this requirement solves this problem (here, names are prefixed with alt to emphasize the fact this is an alternative interface):

trait AltBorrow<'a, AltBorrowed> {
    fn alt_borrow(&'a self) -> AltBorrowed;
}

trait AltToOwned<'a> {
    type AltOwned: AltBorrow<'a, Self>;
    fn alt_to_owned(&'a self) -> Self::AltOwned;
}

This trait could then be implemented for standard types, eg Vec :

impl<'a, T> AltBorrow<'a, &'a [T]> for Vec<T> {
    fn alt_borrow(&'a self) -> &'a [T] {
        self.as_slice()
    }
}

impl<'a, T> AltToOwned<'a> for &'a [T]
    where T: Clone
{
    type AltOwned = Vec<T>;

    fn alt_to_owned(&'a self) -> Vec<T> {
        self.to_vec()
    }
}

As well as for the Variant enum in question:

impl<'a> AltBorrow<'a, BorrowedVariant<'a>> for OwnedVariant {
    fn alt_borrow(&'a self) -> BorrowedVariant<'a> {
        match self {
            &Variant::Nothing => Variant::Nothing,
            &Variant::SomeInt(value) => Variant::SomeInt(value),
            &Variant::SomeBytes(ref value) => Variant::SomeBytes(value.alt_borrow()),
        }
    }
}

impl<'a> AltToOwned<'a> for BorrowedVariant<'a> {
    type AltOwned = OwnedVariant;

    fn alt_to_owned(&'a self) -> OwnedVariant {
        match self {
            &Variant::Nothing => Variant::Nothing,
            &Variant::SomeInt(value) => Variant::SomeInt(value),
            &Variant::SomeBytes(value) => Variant::SomeBytes(value.alt_to_owned()),
        }
    }
}

Finally, the questions:

  1. Am I misusing the original Borrow / ToOwned concept? Should I use something else to achieve this?
  2. If not, then what are the reasons why the current less-generic interface from std::borrow might have been preferred?

This example on Rust playpen

Got some explanation on #rust IRC.

From aturon :

the short answer is: we'd need higher-kinded types (HKT) to do better here; it should be possible to smoothly "upgrade" to HKT later on, though

(this is a pattern that's come up a few places in the standard library)

(lifting the lifetime to the trait level is a way of encoding HKT, but makes it significantly more awkward to use the trait)

From bluss :

I like your question. That kind of lifetime in a trait hasn't been explored enough IMO but it also has a known bug in the borrow checker right now

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM