Here, for posterity, is my first ever Rust program. It checks the key log on the Upspin Key Server.

extern crate sha2; extern crate reqwest; use std::io::Read; use sha2::{Sha256, Digest}; fn main() { let mut resp = match reqwest::get("https://key.upspin.io/log") { Ok(resp) => resp, Err(e) => panic!(e), }; assert!(resp.status().is_success()); let mut content = String::new(); match resp.read_to_string(&mut content) { Ok(_) => {} Err(e) => panic!(e), } let mut hasher = Sha256::default(); let mut ct = 0; let mut last_hash = "".to_string(); for line in content.lines() { if line.starts_with("SHA256:") { let mut fields = line.split(":"); // skip first token match fields.next() { Some(_) => {} _ => { println!("Bad SHA256 line: {}", line); continue; } }; last_hash = fields.next().unwrap().to_string(); let expected = string_to_u8_array(&last_hash); let output = hasher.result(); assert_eq!(output.as_slice(), expected.as_slice()); } else { hasher = Sha256::default(); hasher.input(line.as_bytes()); let newline = "

".as_bytes(); hasher.input(newline); if last_hash != "" { hasher.input(last_hash.as_bytes()); } } ct += 1; println!("Line {}", ct); } } use std::u8; fn string_to_u8_array(hex: &String) -> Vec<u8> { // Make vector of bytes from octets let mut bytes = Vec::new(); for i in 0..(hex.len() / 2) { let res = u8::from_str_radix(&hex[2 * i..2 * i + 2], 16); match res { Ok(v) => bytes.push(v), Err(e) => { println!("Problem with hex: {}", e); return bytes; } }; } return bytes; }

I found myself sprinkling mut’s and unpack()’s here and there like the mutation unpacking fairy, hoping something would work. I don’t think that how you are supposed to do it, but we’ll see.

People give Go a bad time about the size of it’s compiled files, so I expected this little Rust program to be much smaller. But not so:

$ ls -lh ../target/{debug,release}/upspin -rwxrwxr-x 2 jra jra 23M Jul 21 15:12 ../target/debug/upspin -rwxrwxr-x 2 jra jra 5.1M Jul 21 15:14 ../target/release/upspin

People also say, “Go is fat because it is statically linked”. Pure Rust code is also self contained in the program’s ELF file. But the 5.1 meg release image does not count several quite large shared libraries that it depends on:

$ ldd ../target/release/upspin linux-vdso.so.1 => (0x00007fff319c2000) libssl.so.1.0.0 => /lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007f3d7a6cc000) libcrypto.so.1.0.0 => /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007f3d7a288000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f3d7a083000) librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f3d79e7b000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f3d79c5e000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f3d79a47000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3d7967d000) /lib64/ld-linux-x86-64.so.2 (0x00005555d33b8000)

From a code safety point of view, I find it really troubling that even if we work so hard to make the compiler happy to prove our code is secure, we are still depending on libc and the platform’s OpenSSL. I remember the first time I started digging down into Go and realized that it wasn’t even linking libc, and it gave me a fantastic feeling of finally cutting the cord to the old, dangerous way of programming. With Rust (and hyper) we’re right back to depending on C again. Yuck.

Finally, the incredible speedup from debug to release images was surprising:

$ time ../target/debug/upspin > /dev/null real 0m3.631s user 0m0.888s sys 0m0.020s $ time ../target/release/upspin > /dev/null real 0m2.961s user 0m0.068s sys 0m0.016s

Look at user, not real. Real is dominated by waiting on the far-end server. Real is more timing the TLS decryption, the parsing and the hashing.