Array of generics and static allocation

Hi :wave:
I've just started to study embedded programming with Rust and slammed into a problem with the abstraction model Rust uses. I'm trying to create a multi-protocol radio frequencies scanner, with a simple visualization, so I came to the next design:

  1. There is a Sequence trait with "scan" and "has_next" methods
  2. There is a Receiver trait which abstracts a hardware interface providing a "set_frequency" method
  3. There are a few implementation of this trait each for a specific device
  4. There is a Band struct holding a Receiver plus an array of channels for it (i.e. frequencies), just a set of u32 values, there is an implementation of the Sequence trait for the Band
  5. Finally, there is a Scanner which holds an array of Bands, and there is an implementation of the Sequence trait for the Scanner which simply loops through all of it's Bands.

The problem is, when I'm creating an instance of the Scanner, and passing it an array of Bands it "locks" the generics constraints to the type of a first item in array, i.e.:

pub struct Scanner<R: Receiver, const B: usize, const S: usize> {
    bands: [Band<R, B>; S],
    index: usize,
}

impl<R: Receiver, const B: usize, const S: usize> Scanner<R, B, S> {
    fn new(bands: [Band<R, B>; S]) -> Self {
        return Self {
           bands: bands, 
           index: 0,
        };
    }
}

fn main() {
    let scanner = Scanner::new([
        Band::new(R1::new(), [1, 2]),
        Band::new(R2::new(), [3])
    ]);
}

results in

error[E0308]: mismatched types
   --> src/main.rs:105:19
    |
105 |         Band::new(R2::new(), [3, 4])
    |         --------- ^^^^^^^^^ expected `R1`, found `R2`
    |         |
    |         arguments to this function are incorrect

I've created a full example on the Playground for a better understanding of the context.
As far as I understood this limitation comes from static memory allocation typical for a no_std environments...how may I manage to workaround this issue, probably there is some design pattern for such a scenario?

The reasons for the error are that

  • Rust is statically typed
  • Arrays are homogenous structures, that is, the type of every element must be the same
  • Implementors of a trait are not subtypes of some overarching trait-based type
    • E.g. there is no supertype that has R1 and R2 as subtypes
    • The only subtyping in Rust is around lifetimes and types which are higher-ranked over lifetimes

One way is type erasure via dyn Receiver. The challenge on embedded is that you need some sort of indirection (pointer to the dyn Receiver), and the typical owning pointer types (Box<_>, Arc<_>) aren't available on no_std.[1] Non-owning pointer types like &mut _ are available but may be too restrictive.

(Example where I have added impl Receiver for &mut dyn Receiver.)

Another alternative if all the types are known is to use an enum.

(Example with enum SomeReceiver near the end of the file.)


  1. There may be alternatives in the embedded ecosystem which I am unaware of off the top of my head. ↩ī¸Ž

4 Likes

Throwing this link in here for the enum method

4 Likes

Thank you very much for such a detailed answer! I'm assuming the same applies to the const generics, I just need to declare the channels array as mutable and pass them via a mutable reference, otherwise it'll end up with an error like that in case when the arrays sizes are different:

   --> src/lib.rs:129:37
    |
129 |     let scanner = Scanner::new([b1, b2]);
    |                                     ^^ expected `2`, found `1`
    |
    = note: expected struct `Band<_, 2>`
               found struct `Band<_, 1>`

Yes, different const generics result in different types.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.