Detecting two keys pressed in quick succession
Highlights


Using the libc crate for C data types
No heap allocation of read struct
No (new) panics — better error handling
Event iterator instead of vectors
New types and traits to collect related functionality


Beware - I still haven't gotten this to actually read any events on my machine, but it does compile. That should be good enough, right? ^_^

libc crate

The libc crate should always be used when interoperating with C code. This crate is guaranteed to have the correct sizes for platform-specific types.

Additionally, it already has a definition for timeval, so that was nice.

Less heap allocation

I used mem::uninitialized to create an on-stack version of the struct with no values. I then got a reference to that area as bytes and read directly to that.

Note that I marked that function as unsafe - depending on what T resolves to, it could produce very bad results.

Error handling

Everywhere that had a panic! or assert! was changed into a Result. This allows the caller to decide how to deal with the error. The most awkward place left is in the call to read_c_struct, as it just stops the iterator on failure. Instead, it could return the Result directly and allow its caller to see the failure.

Embrace the Iterator

Rust loves iterators. This seemed like a perfect case for it - you have a stream of events and you want to do something based on them. I reworked your method into an Iterator implementation. This allows the caller to decide how much memory to allocate; in the final code, I believe it's very small. It also allows using the filter method and other iterator adaptors.

New types and traits

Iterators need a type to exist, and I also felt that your hashmap and threshold needed to live together. I also moved the read-specific methods into a new trait that is then implemented for all types that implement Read.

Complete code

There's a few comments scattered about for smaller things, but here it is:

extern crate libc;

use std::{mem, env, process, io, slice};
use std::io::{Read, Write};
use std::fs::File;
use std::collections::HashMap;

// Some helper methods for reading things
trait ReadExtensions : Read {
    /// Completely fill the supplied buffer
    fn read_complete(&amp;mut self, buf: &amp;mut [u8]) -&gt; io::Result&lt;()&gt; {
        let mut total_read = 0;
        while total_read &lt; buf.len() {
            match self.read(&amp;mut buf[total_read..]) {
                // Unable to read anything
                Ok(0) =&gt; return Err(io::Error::new(io::ErrorKind::Other, "Unable to read")),
                // Read some amount
                Ok(n) =&gt; total_read += n,
                // Might have been interrupted by something
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted =&gt; {},
                Err(e) =&gt; return Err(From::from(e))
            }
        }
        Ok(())
    }

    /// Directly read a C struct.
    ///
    /// Unsafe because only the concrete type of `T` can determine if
    /// the result is safe. Should only be used to read structs
    /// composed of primitive types without any pointers.
    unsafe fn read_c_struct&lt;T&gt;(&amp;mut self) -&gt; io::Result&lt;T&gt; {
        // avoid allocating on the heap
        let mut val = mem::uninitialized();
        let ptr = &amp;mut val as *mut T as *mut u8;
        let mut buf = slice::from_raw_parts_mut(ptr, mem::size_of::&lt;T&gt;());

        try!(self.read_complete(buf));
        Ok(val)
    }
}

impl&lt;R&gt; ReadExtensions for R where R: io::Read {}



const EV_KEY: u16 = 0x01;

fn to_microseconds(tv: libc::timeval) -&gt; u64 {
    // u64 suffix unneeded, adding _ for readability
    tv.tv_sec as u64 * 1_000_000 + tv.tv_usec as u64
}

// Using libc
#[derive(Copy,Clone)]
#[repr(C)]
struct LinuxInputEvent {
    time: libc::timeval,
    type_: libc::uint16_t,
    code: libc::uint16_t,
    value: libc::int32_t,
}

/// An iterator of Linux events
struct InputSource {
    file: File,
}

impl InputSource {
    fn new(path: &amp;str) -&gt; io::Result&lt;InputSource&gt; {
        let f = try!(File::open(path));
        Ok(InputSource { file: f })
    }
}

impl Iterator for InputSource {
    type Item = LinuxInputEvent;

    fn next(&amp;mut self) -&gt; Option&lt;LinuxInputEvent&gt; {
        unsafe { self.file.read_c_struct() }.ok()
    }
}

/// Tracks when multiple presses of the same key occurs and calls the
/// callback when duplicate events are triggered within a threshold.
struct KeyEventSink {
    last_occurences: HashMap&lt;u16, u64&gt;,
    threshold: u64,
}

impl KeyEventSink {
    fn new(threshold: u64) -&gt; KeyEventSink {
        KeyEventSink {
            last_occurences: HashMap::new(),
            threshold: threshold,
        }
    }

    // Take a closure so we don't have to worry about user interface here
    fn process_event&lt;F&gt;(&amp;mut self, event: LinuxInputEvent, f: F)
        where F: FnOnce(u16, u64)
    {
        let key = event.code;
        let micros = to_microseconds(event.time);

        // if-let for one useful match arm
        if let Some(last_micros) = self.last_occurences.get(&amp;key) {
            let delta = micros - last_micros;
            if delta &lt; self.threshold {
                f(key, delta)
            }
        }
        self.last_occurences.insert(key, micros);
    }
}

fn print_usage_and_exit(program_name: &amp;str) {
    writeln!(&amp;mut io::stderr(), "Usage: {} &lt;microseconds-delta&gt;", program_name).unwrap();
    process::exit(1);
}

fn main() {
    let args: Vec&lt;_&gt; = env::args().collect();
    if args.len() != 2 { print_usage_and_exit(&amp;args[0]); }
    let threshold: u64 = args[1].parse().unwrap_or(0);
    if threshold == 0 { print_usage_and_exit(&amp;args[0]); }

    // just use value of match
    let input = match InputSource::new("/dev/input/event2") {
        Ok(f) =&gt; f,
        Err(err) =&gt; panic!("Unable to open file: {}", err),
    };

    let mut sink = KeyEventSink::new(threshold);

    let key_events = input.filter(|&amp;event| event.type_ == EV_KEY &amp;&amp; event.value == 1);
    for key_event in key_events {
        sink.process_event(key_event, |key, delta| {
            println!("Key code {:3}: event delta: {} μs", key, delta);
        })
    }
}


Followups


  is there any particular reason you added the Copy and Clone traits to LinuxInputEvent? 


Basically, LinuxInputEvent is a simple type comprised of Copyable values, and it's also fairly small in the grand scheme of things. Making it Copy simply improves the ergonomics of using it. If you implement Copy, you need to implement Clone.


  Seeing as the function is used at most once, FnOnce would also be better than Fn


Absolutely correct - I updated to use this.

I also updated the read_complete method a bit based on feedback from this Stack Overflow question.