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.

1 Like

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.