Instantly share code, notes, and snippets.

Embed
What would you like to do?
18F Tech Talk 2017. Building a time-tracking tool in Rust

Building a time-tracking tool in Rust

 _____     _ _   _ _
| __  |_ _|_| |_| |_|___ ___    ___
| __ -| | | | | . | |   | . |  | .'|
|_____|___|_|_|___|_|_|_|_  |  |__,|
 _   _               _  |___|        _
| |_|_|_____ ___ ___| |_ ___ ___ ___|_|___ ___
|  _| |     | -_|___| '_| -_| -_| . | |   | . |
|_| |_|_|_|_|___|   |_,_|___|___|  _|_|_|_|_  |
 _           _    _        _____|_|      _|___|
| |_ ___ ___| |  |_|___   | __  |_ _ ___| |_
|  _| . | . | |  | |   |  |    -| | |_ -|  _|
|_| |___|___|_|  |_|_|_|  |__|__|___|___|_|

A tech-talk about how I wrote Tick in Rust during a time of existential crisis. Spinning a negative experience into a positive one by going from experiencing chaos into exerting control.

Wait, what's this talk about?

    ....      ..                                                ..                             .x+=:.        s
  +^""888h. ~"888h                                        x .d88"                     oe      z`    ^%      :8
 8X.  ?8888X  8888f                    u.    .d``          5888R                    .@88         .   <k    .88
'888x  8888X  8888~       .u     ...ue888b   @8Ne.   .u    '888R        .u      ==*88888       .@8Ned8"   :888ooo
'88888 8888X   "88x:   ud8888.   888R Y888r  %8888:u@88N    888R     ud8888.       88888     .@^%8888"  -*8888888
 `8888 8888X  X88x.  :888'8888.  888R I888>   `888I  888.   888R   :888'8888.      88888    x88:  `)8b.   8888
   `*` 8888X '88888X d888 '88%"  888R I888>    888I  888I   888R   d888 '88%"      88888    8888N=*8888   8888
  ~`...8888X  "88888 8888.+"     888R I888>    888I  888I   888R   8888.+"         88888     %8"    R88   8888
   x8888888X.   `%8" 8888L      u8888cJ888   uW888L  888'   888R   8888L           88888      @8Wou 9%   .8888Lu=
  '%"*8888888h.   "  '8888c. .+  "*888*P"   '*88888Nu88P   .888B . '8888c. .+      88888    .888888P`    ^%888*
  ~    888888888!`    "88888%      'Y"      ~ '88888F`     ^*888%   "88888%        88888    `   ^"F        'Y"
       X888^"""         "YP'                   888 ^         "%       "YP'      '**%%%%%%**
       `88f                                    *8E
        88                                     '8>
        ""                                      "
    .....                                                                           ..                                                                        ..
 .H8888888h.  ~-.                         .uef^"                              x .d88"                            ..             .--~*teu.                   dF
 888888888888x  `>                      :d88E          u.    u.          u.    5888R          u.                @L             dF     988Nx     u.    u.   '88bu.
X~     `?888888hx~      .u          .   `888E        x@88k u@88c.  ...ue888b   '888R    ...ue888b       uL     9888i   .dL    d888b   `8888>  x@88k u@88c. '*88888bu
'      x8.^"*88*"    ud8888.   .udR88N   888E .z8k  ^"8888""8888"  888R Y888r   888R    888R Y888r  .ue888Nc.. `Y888k:*888.   ?8888>  98888F ^"8888""8888"   ^"*8888N
 `-:- X8888x       :888'8888. <888'888k  888E~?888L   8888  888R   888R I888>   888R    888R I888> d88E`"888E`   888E  888I    "**"  x88888~   8888  888R   beWE "888L
      488888>      d888 '88%" 9888 'Y"   888E  888E   8888  888R   888R I888>   888R    888R I888> 888E  888E    888E  888I         d8888*`    8888  888R   888E  888E
    .. `"88*       8888.+"    9888       888E  888E   8888  888R   888R I888>   888R    888R I888> 888E  888E    888E  888I       z8**"`   :   8888  888R   888E  888E
  x88888nX"      . 8888L      9888       888E  888E   8888  888R  u8888cJ888    888R   u8888cJ888  888E  888E    888E  888I     :?.....  ..F   8888  888R   888E  888F
 !"*8888888n..  :  '8888c. .+ ?8888u../  888E  888E  "*88*" 8888"  "*888*P"    .888B .  "*888*P"   888& .888E   x888N><888'    <""888888888~  "*88*" 8888" .888N..888
'    "*88888888*    "88888%    "8888P'  m888N= 888>    ""   'Y"      'Y"       ^*888%     'Y"      *888" 888&    "88"  888     8:  "888888*     ""   'Y"    `"888*""
        ^"***"`       "YP'       "P'     `Y"   888                               "%                 `"   "888E         88F     ""    "**"`                     ""
                                              J88"                                                 .dWi   `88E        98"
                                              @%                                                   4888~  J8%       ./"
                                            :"                                                      ^"===*"`       ~`

010100001100101011011110111000011011001100101 001100010111001101110100
0101010011001010110001101101000110111011011110110110011011110110011101111001 0011001011011101100100

Chaos vs. Control

 ____    __
/\  _`\ /\ \
\ \ \/\_\ \ \___      __      ___    ____
 \ \ \/_/\ \  _ `\  /'__`\   / __`\ /',__\
  \ \ \L\ \ \ \ \ \/\ \L\.\_/\ \L\ /\__, `\
   \ \____/\ \_\ \_\ \__/.\_\ \____\/\____/
    \/___/  \/_/\/_/\/__/\/_/\/___/ \/___/
 __  __    __  _ __  ____  __  __   ____
/\ \/\ \ /'__`/\`'__/',__\/\ \/\ \ /',__\
\ \ \_/ /\  __\ \ \/\__, `\ \ \_\ /\__, `\
 \ \___/\ \____\ \_\/\____/\ \____\/\____/
  \/__/  \/____/\/_/\/___/  \/___/ \/___/
 ____                  __              ___
/\  _`\               /\ \__          /\_ \
\ \ \/\_\   ___    ___\ \ ,_\ _ __  __\//\ \
 \ \ \/_/_ / __`\/' _ `\ \ \//\`'__/ __`\ \ \
  \ \ \L\ /\ \L\ /\ \/\ \ \ \\ \ \/\ \L\ \_\ \_
   \ \____\ \____\ \_\ \_\ \__\ \_\ \____/\____\
    \/___/ \/___/ \/_/\/_/\/__/\/_/\/___/\/____/

"That is my greatest fear That if, if I lost control Or did not have control, things would just, you know I would be...fatal" ~ SZA's mother

rs-sza-v1-caef9ee6-c938-4f82-a50d-f05fb2fef08e

That quote resonates with me a lot. I find myself constantly pulling between feeling in control and relinquishing control. The idea for Tick was born during one of these times all the way back in 2015 when I first moved to Richmond, VA. I found myself in a new city, as a new father, in a completely new job. The company was a very small WordPress agency. I was hired as a JavaScript engineer because they wanted to branch off of WordPress and possibly offer richer interactions for their customers.

The job though was everything but what my expertise was in, and I had a bad time. I also was very focused and paranoid around my time-keeping abilities because how slow I was being told I was when working on their client work. So, I got the bright idea to teach myself Vim and Tmux. Which is another talk entirely. But, because of this desire to seek control in places where I lack it, I persevered and ended up being inspired by a talk from an Omaha technology meetup on Vim + Tmux to start tracking my time whenever I attached and detached from a Tmux session.

This led me to writing a small wrapper around Tmux called Tux which I still use today. The original implementation here used a library called Clocker to track my time. While it got the job done, I always wanted to write my own time-keeping solution and knew it could be more performant if it wasn't written in JavaScript. So I began playing around with the idea of writing Tick in a new language.

Tick

 __ __| _)        |
    |    |   __|  |  /
    |    |  (       <
   _|   _| \___| _|\_\

"But time's a-wasting and evil's out there making hand-crafted mischief for the swap meet of villainy." ~ The Tick

tick_101_06087_rt2_s3_rgb 0

The devil makes work for idle hands must have been coined by an agency executive. Or at least that's how I've felt at every agency I've worked at. The idea being that things take time, time costs money, tracking and estimating time is important for the finances but can be at ends with actual production. So therefore it's super important to make sure you track your time effectively so that budgets and accounting will be happy.

So using Clocker as a starting point for UX, I decided to sketch out the functionality. You can use this today by installing Tick yourself. I like to think of Tick as more of a journal than a time-keeping solution so I also kept the ability to add start and end messages.

Start a timer

$ tick [ --verbose ] start --name my-timer [ --message "I can do the thing!" ]

Check status of a timer

$ tick [ --verbose ] status

Stop a timer

$ tick [ --verbose ] stop --name my-timer [ --message "I did the thing!" ]
$ tick [ --verbose ] stop [ --message "I did the thing!" ] # without a name argument stops the latest running timer

List all timers

$ tick [ --verbose ] list

Remove a timer

$ tick [ --verbose ] remove --id $( tick list | tail -1 | awk '{ print $1 }' ) # delete the latest timer by Timer ID

Rust

                                __.....__
                       _.._ .-''         '.
                     .' .._/     .-''"'-.  `.
              __     | '  /     /________\   \
       _   .:--.'. __| |__|                  |
     .' | / |   \ |__   __\    .-------------',.--.
    .   | `" __ | |  | |   \    '-.____...---//    \
  .'.'| |//.'.''| |  | |    `.             .'\\     |
.'.'.-'  // /   | |_ | |      `''-...... -'   `'-) /
.'   \_.' \ \._,\ '/ | |                        /.'
           `--'  `"  |_|
       _..._      .-'''-.                    _..._
    .-'_..._''.  '   _    \               .-'_..._''.
  .' .'      './   /` '.   \   _..._    .' .'      '.\                            __.....__       _..._
 / .'         .   |     \  ' .'     '. / .'                                   .-''         '.   .'     '.
. '           |   '      |  .   .-.   . '                     .-,.--..-,.--. /     .-''"'-.  `..   .-.   .    .|
| |           \    \     / /|  '   '  | |                     |  .-. |  .-. /     /________\   |  '   '  |  .' |_
| |            `.   ` ..' / |  |   |  | |              _    _ | |  | | |  | |                  |  |   |  |.'     |
. '               '-...-'`  |  |   |  . '             | '  / || |  | | |  | \    .-------------|  |   |  '--.  .-,.--.
 \ '.          .            |  |   |  |\ '.          .' | .' || |  '-| |  '- \    '-.____...---|  |   |  |  |  |//    \
  '. `._____.-'/            |  |   |  | '. `._____.-'/  | /  || |    | |      `.             .'|  |   |  |  |  |\\     |
    `-.______ /             |  |   |  |   `-.______ |   `'.  || |    | |        `''-...... -'  |  |   |  |  |  '.`'-) /
             `              |  |   |  |            `'   .'|  '|_|    |_|                       |  |   |  |  |   /  /.'
                            '--'   '--'      _..._   `-'  `--'      _..._                      '--'   '--'  `'-'
                                          .-'_..._''.            .-'_..._''.         .---.
_________   _...._                      .' .'      '.\    .--. .' .'      '.\        |   |
\        |.'      '-.                  / .'               |__|/ .'                   |   |
 \        .'```'.    '..-,.--.        . '              .| .--. '                     |   |
  \      |       \     |  .-. |   __  | |            .' |_|  | |                __   |   |
   |     |        |    | |  | |.:--.'.| |          .'     |  | |             .:--.'. |   |
   |      \      /    .| |  | / |   \ . '         '--.  .-|  . '            / |   \ ||   |,.--.
   |     |\`'-.-'   .' | |  '-`" __ | |\ '.          |  | |  |\ '.          `" __ | ||   //    \
   |     | '-....-'`   | |     .'.''| | '. `._____.-'|  | |__| '. `._____.-'/.'.''| ||   \\     |
  .'     '.            | |    / /   | |_  `-.______ /|  '.'      `-.______ // /   | |'---'`'-) /
'-----------'          |_|    \ \._,\ '/           ` |   /                ` \ \._,\ '/      /.'
.-,.--.                     .| `--'  `"              `'-'                    `--'  `"
|  .-. |                  .' |_
| |  | |_    _        _ .'     |
| |  | | '  / |     .' '--.  .-'
| |  '.' | .' |    .   | /|  |
| |   /  | /  |  .'.'| |//|  |
| |  |   `'.  |.'.'.-'  / |  '.'
|_|  '   .'|  '.'   \_.'  |   /
  `-'  `--'           `'-'

Rust logo

Lifetimes

Lifetimes are what make Rust Rust source

Rust doesn't have a garbage collector. So in order to be aware of when references can be deallocated, it relies on something called Lifetimes at compile time.

These Lifetimes are the weirdest thing about Rust for me. I had a really hard time wrapping my head around them for a while. And the fact that they're inferred by the compiler most of the time makes it really hard for newcomers to grok it. I use them in Tick for my Struct for creating new Timers since I'm passing user input into SQLite and need to make sure that the data I pass in lives long enough as I pass it around the application.

Learn more about them from this handy reference.

Macros

I really like Macros because they allow me, someone new to systems programming, to use abstractions I'm familiar with, or not familiar with, when building out the functionality around Tick.

However, the status of macro invocations as first-class members of the AST means that the Rust parser has to be able to parse them into something sensible, even when they use syntax that Rust itself doesn't support. source

A great example of Macros in Rust is the JSON implementation.

let mut data = object!{
    "foo" => false,
    "bar" => json::Null,
    "answer" => 42,
    "list" => array![json::Null, "world", true]
};

// Partial equality is implemented for most raw types:
assert!(data["foo"] == false);

// And it's type aware, `null` and `false` are different values:
assert!(data["bar"] != false);

// But you can use any Rust number types:
assert!(data["answer"] == 42);
assert!(data["answer"] == 42.0);
assert!(data["answer"] == 42isize);

// Access nested structures, arrays and objects:
assert!(data["list"][0].is_null());
assert!(data["list"][1] == "world");
assert!(data["list"][2] == true);

// Error resilient - accessing properties that don't exist yield null:
assert!(data["this"]["does"]["not"]["exist"].is_null());

// Mutate by assigning:
data["list"][0] = "Hello".into();

// Use the `dump` method to serialize the data:
assert_eq!(data.dump(), r#"{"foo":false,"bar":null,"answer":42,"list":["Hello","world",true]}"#);

// Or pretty print it out:
println!("{:#}", data);

Match

Matches are really great in Rust. They really help with flow control and I use them extensively in Tick.

Often, a simple if/else isn’t enough, because you have more than two possible options. Also, conditions can get quite complex. Rust has a keyword, match, that allows you to replace complicated if/else groupings with something more powerful. source

let x = 5;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    4 => println!("four"),
    5 => println!("five"),
    _ => println!("something else"),
}

Crates package manager for Cargo Crates

Cargo Crates are packages in Rust. They're really similar to Ruby Gems. But instead of a Gemfile it uses TOML. This is probably the best part of the Rust community for a beginner to systems-programming like myself. With things like Macros and Cargo, I'm able to write a lot of things that would normally be out of reach for me in a language like Rust.

Tick source walk-through

  1. https://github.com/rogeruiz/tick
  2. https://github.com/rogeruiz/tick/blob/master/Cargo.toml
  3. https://github.com/rogeruiz/tick/blob/master/src/config.yml
  4. https://github.com/rogeruiz/tick/blob/master/src/schema.rs
  5. https://github.com/rogeruiz/tick/blob/master/src/models.rs
  6. https://github.com/rogeruiz/tick/blob/master/src/main.rs

Citations

This talk wouldn't be possibly without the valuable information in all these links.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment