Let’s write a GIF decoder in Rust! (Part 1)

Introduction

Rust has been my favorite language to play with for the past couple of years. The learning process has been quite involved. As you may already know, Rust tend not to be one of those programming languages that are easy to learn once you’re comfortable with C/C++ or Java or <insert your favorite language>. At least from my experience, it seems to require a lot of patience and curiosity for deep understanding of programming concepts in order to grasp why Rust works the way it does, and I’m still learning. This blog post is not about why you should use Rust (or why you shouldn’t for that matter). It is about GIFs, how they work, and writing our own working version of a GIF decoder. I challenged myself to write a GIF decoder at one point, almost randomly, and decided to write it in Rust because I thought it’d be more fun that way!

TL;DR

Parser

Let’s start by parsing the GIF header. We’ll represent the header with the following struct:

pub(crate) struct Header {
    pub(crate) sig: String,
    pub(crate) version: String,
}

The header struct is very simple: it will hold the signature value (which must be GIF), and the version (i.e. 89a).

Now let’s create a struct to represent the parser:

use std::io::Read;

pub(crate) struct Parser<'a, T: Read> {
    src: &'a mut T,
}

impl<'a, T: Read> Parser<'a, T> {
    pub(crate) fn new(src: &'a mut T) -> Self {
        Self { src }
    }

    // ...
}

The Parser struct holds a mutable reference to any type that implements the Read trait. We use a generic type T implementing Read trait (instead of &[u8], for example) in order to make the Parser flexible enough to accept any “readable” source (network, file, etc).

Now, let’s create some helper functions to read the bytes from the src.

use std::io::Read;
use std::mem;

impl<'a, T: Read> Parser<'a, T> {

    #[inline(always)]
    fn read_bytes(&mut self, buffer: &mut [u8]) -> Result<(), String> {
        self.src
            .read_exact(buffer)
            .map_err(|e| format!("Error: {}", e))
    }

    #[inline(always)]
    fn read_u8(&mut self) -> Result<u8, String> {
        let mut buffer = [0u8; 1];
        self.read_bytes(&mut buffer)?;
        Ok(buffer[0])
    }

    #[inline(always)]
    fn read_u16(&mut self) -> Result<u16, String> {
        let mut buffer = [0u8; 2];
        self.read_bytes(&mut buffer)?;
        Ok(unsafe { mem::transmute(buffer) })
    }
}

You may have noticed the unsafe keyword. I know usage of unsafe is frowned upon in Rust land but I believe this use case is fine. If this unsafe does make you feel uncomfortable, here’s an alternative:

let mut result = buffer[0] as u16;
result = (result << 8) | buffer[1] as u16; 
Ok(result)

// to be continued

References