Why does this array-to-slice coercion not work?

I have the following example which does not work. Is there any nicer way to convert an array to a slice, other than .as_ref()? It does not seem very ergonomic to me.

struct A();

impl From<&[i32]> for A {
    fn from(i: &[i32]) -> A {
        A{}
    }
}

fn foo(a: impl Into<A>) {}

fn main() {
    foo(&[1, 2, 3, 4]);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `A: std::convert::From<&[{integer}; 4]>` is not satisfied
  --> src/main.rs:11:5
   |
9  | fn foo(arr: impl Into<A>) {}
   |    ---           ------- required by this bound in `foo`
10 | fn main() {
11 |     foo(&[1, 2, 3, 4]);
   |     ^^^ the trait `std::convert::From<&[{integer}; 4]>` is not implemented for `A`
   |
   = help: the following implementations were found:
             <A as std::convert::From<&[i32]>>
   = note: required because of the requirements on the impl of `std::convert::Into<A>` for `&[{integer}; 4]`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

You can use slicing:

foo(&[1, 2, 3, 4][..]);

Alternatively if you make the constructor a concrete function instead of one that goes through a trait, it should automatically convert the reference to the array. It can't in this case, because there are too many generics involved.

Thanks for the suggestion, but that does not seem much better...

I would like that my function foo accepts both a constructed object and a more user-friendly array. I can get this if I accept &[i32] in the function, but then constructed objects do not work anymore. Why does the same conversion not work with traits?

I either have to use .as_ref() or A::new() just to pass 2 values...

It's because the conversion might not be unique. You want to go &[u8; 3]&[u8]A, but if someone adds an From<&[u8; 3]> in the future, your code would silently change behaviour. The compiler prevents this kind of silent behaviour change by not doing the conversion in this case.

1 Like

Thanks for the explanation! It's not possible to implement a From trait with generic array length, right? I looked around but found no way, even with generics.

No, sorry, that feature is not yet stable. You can use a macro to implement it for many lengths, and the standard library typically does so up to 32 items for most uses:

macro_rules! impl_array_to_a {
    ($( $len:literal ),*) => {
        $(
        impl From<&[i32; $len]> for A {
            fn from(i: &[i32; $len]) -> A {
                A{}
            }
        }
        )*
    };
}

impl_array_to_a!(0, 1, 2, 3, 4, 5, 6);

Another possible solution is that instead of implementing From<&[i32]> for A, you could implement From<T> for A where T: AsRef<[i32]>. As you noted, you can call as_ref to view an array as a slice, so instead of making the user call as_ref you could call as_ref in the From implementation. Additionally, using the fact that AsRef<[i32]> is also implemented for &[i32], it still functions if you pass a slice to the function (as it should). As a nice bonus, the function would also accept Vec<i32> and any other type which implements AsRef<[i32]>.
Here's a playground with an example implementation.

3 Likes

Woa, that's exactly what I wanted! Thanks a lot!

So is it good practice to implement From from references as generics with AsRef? It seems like that would accommodate many more cases that something like my naive implementation.

It can be a great idea, although be aware that it makes adding other From impls difficult because the compiler might worry someone adds an AsRef<[i32]> impl to that other type in the future, making them conflict.

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