Structs / refs / equiv?

EDIT: I made a typo. Originally 'struct', but it should have been 'enum'.

pub enum Foo {
  A(A),
  B(B),
  C(C),
}

pub enum Bar<'a> {
  A(&'a A),
  B(&'a B),
  C(&'a C)
}

It seems that for many functions that take an &Foo, we could just as well pass it a Bar, and it should still work.

Is there a name for this property and a way to exploit it?

Context: I have a &'a B, I want to construct a &Foo, but I don't want to clone the B (or maybe it can't even be cloned).

1 Like

I would call Bar<'a> a view type of Foo.

You context is unclear to me. You can't safely go from a Bar<'a> to a &'a Foo though; it could be pointing to different Foo fields or things that are part of no Foo. You can go the other way.

Here's a related topic which might apply:

1 Like
  1. I made a mistake. I wrote Foo/Bar as structs. They should be enums (now fixed).

  2. Here is the context:

We have a &'a B. From this, we can construt a Bar<'a>, but I have a function that only takes a &Foo.

You generally can't because A and Foo-when-variant-A may have different layouts; if you used explicit representations so you know the layouts, then &A would still be missing the discriminant as all explicit enum layouts don't use implicit discriminants.

So I believe it's only possible for enums with guaranteed layout where the discriminant is implicit in the value being well-formed. Probably your enum is not one.

In a generic context, it wouldn't need the layout to be the same.

I think the Borrow trait is flawed. HashSet<String> can use Borrow to look up by &str, but HashSet<(String, String)> can't be looked up by (&str, &str).

2 Likes

The "flawed" is due to the lack of GAT on Rust 1.0 release. But we have GAT in stable now, are there any community effort to address this issue?

It's more generic type constructors/parameters in this case.

trait BetterBorrow<T, X<'*, *>> {
    fn better_borrow(&self) -> X<'_, T>;
}

It can be somewhat approximated today...

trait BetterBorrowConstructor {
    type Bbc<'a, T: ?Sized> where T: 'a;
}

trait BetterBorrow<T: ?Sized, B: BetterBorrowConstructor> {
    fn better_borrow(&self) -> B::Bbc<'_, T>;
}

struct Reference;
impl BetterBorrowConstructor for Reference {
    type Bbc<'a, T: ?Sized> = &'a T where T: 'a;
}

impl<T: ?Sized, U: ?Sized> BetterBorrow<T, Reference> for U where U: Borrow<T> {
    fn better_borrow(&self) -> &T { self.borrow() }
}

But this clearly isn't a drop-in replacement, and there are some rough edges around where GAT is at (probably some HRTB restrictions, isn't trait object safe, etc). Maybe also because Bbc can't name a restriction based on the implementor of BetterBorrow without even more hoops (where Self: 'a is the wrong bound for the example).

(Might also be the wrong shape for other reasons like variadic generics? Haven't spent a ton of time thinking about it.)


For the associated type flavor, there's either a long way to go to support some ?GAT thing that's also trait object safe somehow, or you define a different trait.

trait CanonicalBorrow {
    type Canon;
    fn c_borrow(&self) -> &Self::Canon;
}

trait BetterCanonicalBorrow {
    type Canon<'a> where Self: 'a;
    fn c_borrow(&self) -> Self::Canon<'_>;
}

// Depending on the use case, this might not be desired...
// (Breaking change for existing CanonicalBorrow implementors
//  to migrate to something better)
impl<T: CanonicalBorrow> BetterCanonicalBorrow for T {
    type Canon<'a> = &'a <Self as CanonicalBorrow>::Canon where Self: 'a;
    fn c_borrow(&self) -> Self::Canon<'_> {
        <Self as CanonicalBorrow>::c_borrow(self)
    }
}

Alternatively, one can define more targeted traits.

It’s a bit like String vs &str / &Vec<T> vs &[T]. And it’s a bit like &Option<T> vs Option<&T>.

I think, having a type like Bar<'_> can be a good idea; I don’t know of a name for this concept. Maybe call Bar<'_> something like FooRef<'_>, and give Foo an as_ref(&self) -> FooRef<'_> method (method name inspired by Option’s method.