_____ _ _ _ _
| __ |_ _|_| |_| |_|___ ___ ___
| __ -| | | | | . | | | . | | .'|
|_____|___|_|_|___|_|_|_|_ | |__,|
_ _ _ |___| _
| |_|_|_____ ___ ___| |_ ___ ___ ___|_|___ ___
| _| | | -_|___| '_| -_| -_| . | | | . |
|_| |_|_|_|_|___| |_,_|___|___| _|_|_|_|_ |
_ _ _ _____|_| _|___|
| |_ ___ ___| | |_|___ | __ |_ _ ___| |_
| _| . | . | | | | | | -| | |_ -| _|
|_| |___|___|_| |_|_|_| |__|__|___|___|_|
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.
.... .. .. .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
____ __
/\ _`\ /\ \
\ \ \/\_\ \ \___ __ ___ ____
\ \ \/_/\ \ _ `\ /'__`\ / __`\ /',__\
\ \ \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
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.
__ __| _) |
| | __| | /
| | ( <
_| _| \___| _|\_\
"But time's a-wasting and evil's out there making hand-crafted mischief for the swap meet of villainy." ~ The Tick
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.
$ tick [ --verbose ] start --name my-timer [ --message "I can do the thing!" ]
$ tick [ --verbose ] status
$ 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
$ tick [ --verbose ] list
$ tick [ --verbose ] remove --id $( tick list | tail -1 | awk '{ print $1 }' ) # delete the latest timer by Timer ID
__.....__
_.._ .-'' '.
.' .._/ .-''"'-. `.
__ | ' / /________\ \
_ .:--.'. __| |__| |
.' | / | \ |__ __\ .-------------',.--.
. | `" __ | | | | \ '-.____...---// \
.'.'| |//.'.''| | | | `. .'\\ |
.'.'.-' // / | |_ | | `''-...... -' `'-) /
.' \_.' \ \._,\ '/ | | /.'
`--' `" |_|
_..._ .-'''-. _..._
.-'_..._''. ' _ \ .-'_..._''.
.' .' './ /` '. \ _..._ .' .' '.\ __.....__ _..._
/ .' . | \ ' .' '. / .' .-'' '. .' '.
. ' | ' | . .-. . ' .-,.--..-,.--. / .-''"'-. `.. .-. . .|
| | \ \ / /| ' ' | | | .-. | .-. / /________\ | ' ' | .' |_
| | `. ` ..' / | | | | | _ _ | | | | | | | | | | |.' |
. ' '-...-'` | | | . ' | ' / || | | | | | \ .-------------| | | '--. .-,.--.
\ '. . | | | |\ '. .' | .' || | '-| | '- \ '-.____...---| | | | | |// \
'. `._____.-'/ | | | | '. `._____.-'/ | / || | | | `. .'| | | | | |\\ |
`-.______ / | | | | `-.______ | `'. || | | | `''-...... -' | | | | | '.`'-) /
` | | | | `' .'| '|_| |_| | | | | | / /.'
'--' '--' _..._ `-' `--' _..._ '--' '--' `'-'
.-'_..._''. .-'_..._''. .---.
_________ _...._ .' .' '.\ .--. .' .' '.\ | |
\ |.' '-. / .' |__|/ .' | |
\ .'```'. '..-,.--. . ' .| .--. ' | |
\ | \ | .-. | __ | | .' |_| | | __ | |
| | | | | | |.:--.'.| | .' | | | .:--.'. | |
| \ / .| | | / | \ . ' '--. .-| . ' / | \ || |,.--.
| |\`'-.-' .' | | '-`" __ | |\ '. | | | |\ '. `" __ | || // \
| | '-....-'` | | .'.''| | '. `._____.-'| | |__| '. `._____.-'/.'.''| || \\ |
.' '. | | / / | |_ `-.______ /| '.' `-.______ // / | |'---'`'-) /
'-----------' |_| \ \._,\ '/ ` | / ` \ \._,\ '/ /.'
.-,.--. .| `--' `" `'-' `--' `"
| .-. | .' |_
| | | |_ _ _ .' |
| | | | ' / | .' '--. .-'
| | '.' | .' | . | /| |
| | / | / | .'.'| |//| |
| | | `'. |.'.'.-' / | '.'
|_| ' .'| '.' \_.' | /
`-' `--' `'-'
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.
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);
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"),
}
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.
- https://github.com/rogeruiz/tick
- https://github.com/rogeruiz/tick/blob/master/Cargo.toml
- https://github.com/rogeruiz/tick/blob/master/src/config.yml
- https://github.com/rogeruiz/tick/blob/master/src/schema.rs
- https://github.com/rogeruiz/tick/blob/master/src/models.rs
- https://github.com/rogeruiz/tick/blob/master/src/main.rs
This talk wouldn't be possibly without the valuable information in all these links.