either icon indicating copy to clipboard operation
either copied to clipboard

Unsized marker for AsRef

Open mexus opened this issue 7 years ago • 11 comments

Apparently AsRef implementation of Either lacks an "unsized" marker ?Sized on Target. It prevents of implementing AsRef<str> for the Either, for instance.

While default Either doesn't compile (without the marker on playground) ...

extern crate either; // 1.5.0
use either::Either;

fn get_default(s: Option<impl AsRef<str>>) -> impl AsRef<str> {
    s.map(Either::Left).unwrap_or_else(|| Either::Right("default"))
}

fn main() {
    println!("{}", get_default(None::<String>).as_ref());
    println!("{}", get_default(Some("not default")).as_ref());
}

... an example with the marker compiles just ok :) with the marker on playground

enum Either2<L, R> {
    Left(L),
    Right(R),
}

impl<L, R, T: ?Sized> AsRef<T> for Either2<L, R>
where
    L: AsRef<T>,
    R: AsRef<T>,
{
    fn as_ref(&self) -> &T {
        match self {
            Either2::Left(x) => x.as_ref(),
            Either2::Right(x) => x.as_ref(),
        }
    }
}

fn get_default(s: Option<impl AsRef<str>>) -> impl AsRef<str> {
    s.map(Either2::Left)
        .unwrap_or_else(|| Either2::Right("default"))
}

fn main() {
    println!("{}", get_default(None::<String>).as_ref());
    println!("{}", get_default(Some("not default")).as_ref());
}

So, my question is: was the ?Sized marker omitted on purpose, or should I make a PR which adds the marker? :)

mexus avatar Sep 06 '18 18:09 mexus

In principle I think it would be fine to have Target: ?Sized on our AsRef, and on AsMut too.

My only hesitation is whether this constitutes a breaking change to expand the scope of the impl. That is, if someone has their own unsized type Foo, can they currently write impl AsRef<Foo> for Either? If so, we'd be adding a conflicting implementation when we make ours unsized.

cuviper avatar Sep 06 '18 19:09 cuviper

I believe it's impossible: playground

extern crate either; // 1.5.0
use either::Either;

struct Unsized {
    data: [u8],
}

impl<L, R> AsRef<Unsized> for Either<L, R>
where
    L: AsRef<Unsized>,
    R: AsRef<Unsized>,
{
    fn as_ref(&self) -> &Unsized {
        match self {
            Either::Left(x) => x.as_ref(),
            Either::Right(x) => x.as_ref(),
        }
    }
}

produces

error[E0210]: type parameter `L` must be used as the type parameter for some local type (e.g. `MyStruct<L>`)
  --> src/lib.rs:8:1
   |
8  | / impl<L, R> AsRef<Unsized> for Either<L, R>
9  | | where
10 | |     L: AsRef<Unsized>,
11 | |     R: AsRef<Unsized>,
...  |
18 | |     }
19 | | }
   | |_^ type parameter `L` must be used as the type parameter for some local type
   |
   = note: only traits defined in the current crate can be implemented for a type parameter

I've made an unsized type just to be more specific, but I believe it doesn't matter in this case.

mexus avatar Sep 06 '18 19:09 mexus

Your example works with concrete types though:

impl AsRef<Unsized> for Either<Box<Unsized>, Box<Unsized>> {
    fn as_ref(&self) -> &Unsized {
        match self {
            Either::Left(x) => x.as_ref(),
            Either::Right(x) => x.as_ref(),
        }
    }
}

cuviper avatar Sep 06 '18 20:09 cuviper

Ah, true! I didn't look that far :( If we allow unsized types in AsRef it will rise a conflict: playground.

So it's a breaking change potentially :cry:

mexus avatar Sep 06 '18 20:09 mexus

I think we could still implement AsRef<str> and AsRef<[T]> though.

cuviper avatar Sep 06 '18 20:09 cuviper

Okay, sounds good! Will send a PR

mexus avatar Sep 06 '18 21:09 mexus

Btw, isn't it a potential-2.0 change? (i mean adding the ?Sized marker)

mexus avatar Jul 04 '19 15:07 mexus

Sure, I've added the label.

cuviper avatar Jul 04 '19 21:07 cuviper

It's a shame that expanding the blanket impl is a breaking change.

nvzqz avatar Jul 28 '19 23:07 nvzqz