Add `impl<T> AsRef<[T]> for T`

Could we add the following blanket implementations to std::convert?

impl<T> AsRef<[T]> for T {
    fn as_ref(&self) -> &[T] {
        unsafe { std::slice::from_raw_parts(self, 1) }
        // UPDATE: or preferably the safe version:
        // std::slice::from_ref(self)
    }
}

impl<T> AsMut<[T]> for T {
    fn as_mut(&mut self) -> &mut [T] {
        unsafe { std::slice::from_raw_parts_mut(self, 1) }
        // UPDATE: or preferably the safe version:
        // std::slice::from_mut(self)
    }
}

This addition would improve ergonomics (and maybe even performance for large types) when calling a function that takes an AsRef by passing a single value instead of having to pass [value]. For example:

fn print_each<T: AsRef<[i32]>>(values: T) {
    for value in values.as_ref() {
        println!("{value}");
    }
}

fn print_some_values() {
    // Currently you'd have to write `print_each([42])`
    print_each(42);
    print_each([11, 22, 33]);
}

If anything, it would only reduce readability IMO. It doesn't affect performance as T and [T; 1] are the exact same size and I think passed the same way in the Rust ABI.

4 Likes

Perhaps a better example to demonstrate the (in my opinion) improved ergonomics:

fn foo(values: &[i32], value: i32) {
    let mut x = MyCollection::from(values);
    
    // Instead of `MyCollection::from([value])`, we _can_ write:
    let y = MyCollection::from(value);

    x.append(values);

    // Instead of `x.append([value])`, we _can_ write:
    x.append(value);
}

Is let t = [t]; optimized to a no-op? I tried in the playground, but Assembly generation isn't working and I can't read HIR/MIR/LLVM IR well enough to determine that it is optimized to a no-op.

rust.godbolt.com is very much recommended for assembly inspection.

5 Likes

Aside, slice::from_ref and from_mut do these conversions safely.

4 Likes

Ah, neat. The underlying impl.

To answer this directly: No, it's a breaking change since I may already have impl AsRef<[MyStruct]> for MyStruct and the blanket implementation would conflict with that.

Adding any blanket impl for an existing trait is a major breaking change.

5 Likes

Theoretically, would a new Rust edition be allowed to make a breaking change such as this one?

Crates have to interoperate across editions, so technically no, editions don't help with the blanket implementation case.

That said, one can dream up approaches that aren't technically a blanket implementation, like structs defined in edition 20XX automatically have AsRef<[Self]> derived unless you opt out. There would have to be an opt-out, because otherwise

(You could then talk about deeper layers of complexity; "It's automatically derived unless you define it"[3] would help with the second bullet but not the first one, so now you need to somehow detect that case too..... but even by this point we're getting into behavior which is IMO overly complicated and too fragile to be tenable.)


One could also have a conversation: If the likely outcome or perhaps even explicit goal is to make one particular implementation so omnipresent that the ecosystem expects it, and thus all crates that are exceptions get pressured to change or be abandoned -- is that morally different from actually making the breaking change or ecosystem split, which you promised to never do?


  1. this also means it would have to be on an edition ↩︎

  2. some crates would choose or be forced to stay on an old edition ↩︎

  3. aping specialization ↩︎

2 Likes

Thank you for a great explanation.

1 Like

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