Generalize a struct with a macro

I tried to generalize a struct while using a macro but failed very early. It would be great if someone could give some hints.

I use the following struct and try to generalize the data part that I can generate different arrays.

struct ArrayIter {
    data: [u8; 512],
    pos: usize,
    end: usize,
}

impl ArrayIter {
    fn new(data: [u8; 512]) -> Self {
        ArrayIter {
            data,
            pos: 0,
            end: 0,
        }
    }
}

impl Iterator for ArrayIter {
    type Item = u8;

    fn next(&mut self) -> Option<Self::Item> {
        if self.pos == self.end {
            self.pos = 0;
            return None;
        }
        let r = self.data[self.pos];
        self.pos += 1;
        Some(r)
    }
}

I'm not sure what you want. If you want to create several structs I would do something like this.
I think it should be possible to create the struct's name from something like format!("ArrayIter{}", $size) but I don't now how.

Maybe the first step I found was to generalize the Type. I can do this if I change the struct to this:

struct ArrayIter<T> {
    data: [T; 512],
    pos: usize,
    end: usize,
}

impl<T> ArrayIter<T> {
    fn new(data: [T; 512]) -> Self {
        ArrayIter {
            data,
            pos: 0,
            end: 0,
        }
    }
}

impl<T: Copy> Iterator for ArrayIter<T> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.pos == self.end {
            self.pos = 0;
            return None;
        }
        let r = self.data[self.pos];
        self.pos += 1;
        Some(r)
    }
}

This works very well, but now I would like to make the length of the data array variable. As far as I know, I have to do this with macros.

Okay for clarification what is your concrete use case?

  1. If you want the array to be resizeable at runtime you should simply use a Vec<T>
  2. If you only miss the T in my solution you can use this

The use case is to try to write an parser without any allocation.
The program should work like this:

  1. read a bunch of data from a file to an array
  2. search for some specific parts in the array (copy the data between "start" and "end" in a second array)
    2b. if the array is processed, read new data
  3. return what I found

For the search part I like to iterate over the array, but I found no easy way to store the Iterator in a struct an now tried to build that part on my own.

Well if you want to omit allocations I would try to borrow the specific parts, e.g.:

let data = [...];
let specific = &data[start..pos]; // specific is now a slice
for element in specific.iter() {
    // ...
}

I think this should solve the problem. However, if you are going to write a parser I highly recommend using a parser library like nom which allows you exactly what you are trying to do.

Thanks for your advice. I will have a look at nom.

The problem with the borrow of specific parts starts when I try to store it in a struct. I always get crazy with the lifetimes. Never found a solution. And as far as I understood, it's not that easy to solve.

With some experience handling lifetimes loses its smell of magic.

The big thing you have to consider is why Rust needs lifetimes. Rust guarantees that a borrow can not outlive its owner. So if you store a borrow somewhere rustc must be able to track this. Rust is designed to be able to track lifetimes solely by reading the type and function signatures.

Here's a little working example.

You may be going about this in the wrong way. If you want to create a parser that doesn't do any allocations then you need to consume the parsed data the moment it is parsed. This is normally done by returning an iterator or via callbacks which are invoked during the parsing process. Storing the results in an array and then processing the results can become tricky and problematic (e.g. how big should I make my buffer?).

I'd break the searching process up into two parts. In the first you'll create some custom Parser struct which borrows from the original input source.

pub struct Parser<'input> {
  src: &'input str,
  current_location: usize,
}

impl<'input> Iterator for Parser<'input> {
  type Item = (Span, Foo<'input>);

  fn next(&mut self) -> Option<Self::Item> {
    // parse the next item, moving `current_location` further along the buffer
    // and using it to generate a `Span` for that item (handy for reporting errors)
  }
}

pub enum Foo<'input> {
  Bar(usize),
  Baz(&'input str),
}

/// The byte indexes defining where a particular item lies in the original source.
pub struct Span {
  pub start: usize,
  pub end: usize,
}

Then you define your search as a function that takes any iterator over Foo<'input>s.

This is the approach I took when writing the gcode crate, a #[no_std] crate which parses gcode programs designed for use on microcontrollers. Feel free to have a look at the repo on GitHub if you want to see how I did this.

1 Like

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