AsRef and AsMut between T and [T; 1]

Would there be any overlap issues with adding these impls to std?

impl<T> AsRef<[T; 1]> for T { ... }
impl<T> AsRef<T> for [T; 1] { ... }
impl<T> AsMut<[T; 1]> for T { ... }
impl<T> AsMut<T> for [T; 1] { ... }
4 Likes

The usual concerns of adding blanked impls apply, i.e. the for T impls are technically breaking changes, since downstream implementations like the following could exist:

struct Foo(u8);

impl AsRef<[Foo; 1]> for Foo {
    fn as_ref(&self) -> &[Foo; 1] {
        std::array::from_ref(self)
    }
}
6 Likes

Ah wait… even worse: even the other way is technically possible, too.

struct Foo(u8);

impl AsRef<Foo> for [Foo; 1] {
    fn as_ref(&self) -> &Foo {
        &self[0]
    }
}
7 Likes

Well, that settles that!

I wonder if there's a way we could carve out a "less bad than specialization" notion of "fallback impls" or something, that could fit for these, for impl<T: Copy> Clone for T { ... }, etc.

1 Like

Note that a breaking change is not necessarily a deal breaker. If it is really desirable, delegating such a change to the next edition, especially if breakage is very little in practice, is entirely doable.

The edition system is made for syntactic changes. It doesn't support conditional trait implementations. The sets of trait implementations crates see has to be consistent with each other to prevent miscompilations. That is why we have the coherence rules to prevent trait implementations that can result in different crates having inconsistent views of how traits are implemented.

7 Likes

Like what @CAD97 suggested here, right?

I'm not sure that proposal is sound either. It seems like that still allows specializing on a lifetime, which is unsound.

2 Likes

Is there a trick for developing unsoundness that doesn't rely on associated types? The proposal in that post requires the specialized trait to only have functions.

(I realize this is getting off-topic, we can break it out into a new topic if desired.)

Without reading that proposal… for just the trait at hand here, and impl like the following would run into all the soundness problems if it were to be combined with a more general impl<T> AsRef<T> for [T; 1]:

struct Foo<'a, 'b, 'c>(&'a mut &'b mut str, &'a mut &'c mut str);
impl<'a, 'bc> AsMut<Foo<'a, 'bc, 'bc>> for [Foo<'a, 'bc, 'bc>; 1] {
    fn as_mut(&mut self) -> &mut Foo<'a, 'bc, 'bc> {
        let result = &mut self[0];
        std::mem::swap(result.0, result.1);
        result
    }
}

Now, either that proposal in the other thread would rule out a situation where the above impl can specialize a impl<T> AsRef<T> for [T; 1], in which case the proposal is inadequate for solving the issue of adding impl<T> AsRef<T> for [T; 1] without breaking changes, or the proposal in the other thread would allow the situation, leading to unsoundness, or yet-again breakage (by observable changes in behavior), depending on which of the impls were to be chosen for as_mut calls on [Foo<'_, '_, '>; 1][1].


  1. assuming we cannot distinguish run-time behavior of that method call based merely on the question whether the receiver type is Foo<'a, 'b, 'c> with 'b == 'c being the same lifetime or vs. them not being the same lifetime… and here’s some more context in case it’s unclear why that’s something we cannot / don’t want to be able to do ↩︎

1 Like

Thank you, this way of framing the issue really made it click for me!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.