Circular FIFO Buffer I was bored the other day and got to wondering how I would implement a circular FIFO buffer in Rust. My goal was to create an implementation I could use on a bare metal microcontroller. This means no dynamic allocation and a fixed size known at compile time, so I used a primitive fixed size array instead of a vector as a backing store.

My next pass at this will be to make the type stored in the FIFO buffer generic and to find a way to specify the capacity, but I wanted to get some feedback before doing so. Am I missing any test cases? What can I improve here?

src/lib.rs

const FIFO_CAPACITY: usize = 32;

pub struct Fifo {
    size: usize,
    read_idx: usize,
    write_idx: usize,
    buffer: [u8; FIFO_CAPACITY]
}

impl Fifo {
    pub fn new() -&gt; Fifo {
        Fifo {
            size: 0,
            read_idx: 0,
            write_idx: 0,
            buffer: [0; FIFO_CAPACITY]
        }
    }

    pub fn push(&amp;mut self, item: u8) -&gt; Result&lt;(), &amp;'static str&gt; {
        if self.buffer_full() {
           Err("Buffer full.")        
        } else {
            self.buffer[self.write_idx] = item;

            self.write_idx = Fifo::increment_index(self.write_idx);
            self.size = self.size + 1;

            Ok(())
        }
    }

    pub fn pop(&amp;mut self) -&gt; Option&lt;u8&gt; {
        if self.size == 0 {
            None
        } else {
            let result = self.buffer[self.read_idx];

            self.read_idx = Fifo::increment_index(self.read_idx); 
            self.size = self.size - 1;

            Some(result)        
        }
    }

    pub fn buffer_full(&amp;self) -&gt; bool {
        self.size == FIFO_CAPACITY
    }

    fn increment_index(idx: usize) -&gt; usize {
        (idx + 1) % FIFO_CAPACITY
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pop_item_that_was_pushed_to_buffer() {
        let mut buffer = Fifo::new();
        assert!(buffer.push(5).is_ok());
        let pop_result = buffer.pop();
        assert_eq!(Some(5), pop_result);
    }

    #[test]
    fn popping_empty_buffer_returns_none() {
        let mut buffer = Fifo::new();
        assert_eq!(None, buffer.pop());
    }

    #[test]
    fn popping_returns_first_pushed_first() {
        let mut buffer = Fifo::new();
        assert!(buffer.push(1).is_ok());
        assert!(buffer.push(2).is_ok());

        assert_eq!(Some(1), buffer.pop());
        assert_eq!(Some(2), buffer.pop());
    }

    #[test]
    fn pop_beyond_write_index_returns_none() {
        let mut buffer = Fifo::new();
        assert!(buffer.push(1).is_ok());
        assert_eq!(Some(1), buffer.pop());
        assert_eq!(None, buffer.pop());
    }

    #[test]
    fn pop_beyond_write_index_continuing_on_works() {
        let mut buffer = Fifo::new();
        assert!(buffer.push(1).is_ok());
        assert_eq!(Some(1), buffer.pop());
        assert_eq!(None, buffer.pop());

        assert!(buffer.push(2).is_ok());
        assert_eq!(Some(2), buffer.pop());
    }

    #[test]
    fn buffer_wraps_around() {
        let mut buffer = Fifo::new();
        let capacity = FIFO_CAPACITY as u8;
        for x in 0..=(capacity * 3) {
            assert!(buffer.push(x).is_ok());
            assert_eq!(Some(x), buffer.pop());
        }
    }

    #[test]
    fn push_fails_if_buffer_is_full() {
        let mut buffer = Fifo::new();
        let capacity = FIFO_CAPACITY as u8;

        for x in 0..(capacity) {
            assert!(buffer.push(x).is_ok());
        }

        assert!(buffer.push(capacity + 1).is_err())
    }
}
Your code looks pretty decent. My two cents:


Implement Default

impl Default for Fifo {
    fn default() -&gt; Fifo {
        Fifo {
            size: 0,
            read_idx: 0,
            write_idx: 0,
            buffer: [0; FIFO_CAPACITY],
        }
    }
}


Then you could simplify your new to:

pub fn new() -&gt; Fifo {
    Fifo::default()
}

Simplify expressions

L27: self.size = self.size + 1 will become self.size += 1
L40: self.size = self.size - 1 will become self.size -= 1
Replace str error with enums

pub enum FifoError {
    FifoFull,
}

pub fn push(&amp;mut self, item: u8) -&gt; Result&lt;(), FifoError&gt; {
    if self.buffer_full() {
       Err(FifoError::FifoFull)
    } else {
        ...
}

Add assert_eq!(None, buffer.pop()); to the end of every test where feasible, e,g.


pop_item_that_was_pushed_to_buffer
popping_returns_first_pushed_first
pop_beyond_write_index_continuing_on_works
buffer_wraps_around



For further exercices I would recommend:


Implement Iterator/IntoIterator/FromIterator
Next, implement Debug, which is fairly easy (Hint: Take a look at the implementation of Debug for slice)
Make it accept a generic type