Monday, 8 January 2018

Rust: Lacking equivalence under simple refactorings

I've been writing a fair bit of Rust recently. I'm new to the language, and it's been alternating between making me very happy, and making me very frustrated. One of the things I've been finding frustrating is the number of places that I expect two pieces of code to be equivalent where they are not. Here's some sample code, which I'm going to use in a few examples:

#[derive(Debug)]
struct Foo {}

impl Foo {
    fn foo(&self) -> &Foo {
        self
    }

    fn bar(&mut self) -> &Foo {
        self
    }

    fn baz(&mut self) -> &mut Foo {
        self
    }
}

fn call_one() {
    let bar = Foo{}.bar();
    println!("{:?}", bar);
}

fn call_two() {
    let mut foo = Foo{};
    let bar = foo.bar();
    println!("{:?}", bar);
}

fn call_three() {
    let foo = Foo{}.foo();
    println!("{:?}", foo);
}

fn call_four() {
    let mut f = Foo{};
    let baz = f.baz();
    println!("{:?}", baz);
    let bar = f.bar();
    println!("{:?}", bar);
}

fn call_five() {
    let mut f = Foo{};
    println!("{:?}", f.baz());
    println!("{:?}", f.bar());
}

fn call_six() {
    let mut f = Foo{};
    {
        let baz = f.baz();
        println!("{:?}", baz);
    }
    let bar = f.bar();
    println!("{:?}", bar);
}

First, let's look at call_one and call_two. These look like they should be identical code; the simple application of an "inline" refactoring (or "extract variable", if going in the other direction). But in reality, call_two is fine, but the borrow checker is unhappy with call_one:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:19:26
   |
19 |     let bar = Foo{}.bar();
   |               -----      ^ temporary value dropped here while still borrowed
   |               |
   |               temporary value created here
20 |     println!("{:?}", bar);
21 | }
   | - temporary value needs to live until here
   |
   = note: consider using a `let` binding to increase its lifetime

So what's going on here? When you declare a variable with let, its lifetime begins, and it remains live until the variable it's bound to goes out of scope. But if you never bind an expression to a variable with let, its lifetime immediately ends, and it gets dropped. There seem to be some special-cases where this is transparently handled for you (look at call_three - it's exactly the same as call_one which the borrow checker finds problematic, but the borrow checker is fine with this one. The only difference is that bar borrows self mutably, whereas foo doesn't. I assume that internally, the compiler is somehow promoting foo to be an owned type, or creating a hidden temporary which owns the Foo. It would be nice if the same courtesy could be afforded to more mutable references until something actually tries to violate constraints, like actually modifying the reference, rather than just preventing anonymous mutable borrows entirely).  The big place I've seen this be a problem in real life is with the builder pattern; if I want to populate my builder with some arguments, and then build from it more than once, I need three statements: to assign the un-populated builder, to populate it, and to build it, where I'd really like two: to assign the populated builder, and to build it.

There is work underway in the language to solve part of this problem, with something called non-lexical lifetimes (NLL). Take a look at call_fourcall_five, and call_six. Again, these look like they should be equivalent, but call_four makes the borrow checker unhappy.

error[E0499]: cannot borrow `f` as mutable more than once at a time
  --> src/main.rs:39:15
   |
37 |     let baz = f.baz();
   |               - first mutable borrow occurs here
38 |     println!("{:?}", baz);
39 |     let bar = f.bar();
   |               ^ second mutable borrow occurs here
40 |     println!("{:?}", bar);
41 | }
   | - first borrow ends here

This time, the borrow checker is unhappy for the opposite reason: a variable's lifetime is bounded by its scope, and its scope continues until the end of the block in which it was declared, so baz, which mutably borrows f, precludes bar from being able to borrow it, even though baz could be analysed by the compiler to be dead. You can work around this by avoiding the assignment, as in call_five, or by introducing an inner scope, after which baz gets dropped, as in call_six. The NLL work will address this, and perform the liveness analysis to render these workarounds unnecessary.

Having to re-structure code which otherwise feels like it should be equivalent to satisfy the borrow checker is pretty frustrating, and occasionally means sacrificing clarity. It's great to see work being done to improve this situation, and I hope it continues to go further!


Pixel 2

For the last 6ish years, I’ve primarily been an iPhone user. Every few years, I’ve tried out a flagship Android phone as my main phone for a non-trivial period of time (most recently the Nexus 6), and I’ve always switched back to an iPhone. About six weeks ago, I got a Pixel 2, and I’ve been using it pretty much exclusively. Here are my thoughts on being both an Android user, and a Pixel 2 user.
tl;dr: Android is pretty good - definitely up to iOS quality. The hardware is pretty solid, but lacks a lot of finesse. If I switch back to an iPhone, it will be because of the physical build quality, but I’m not likely to do that at least until this phone dies.

Android

Android has gotten a lot better. The user experience is a lot more consistent and a lot less flaky than it used to be. I’ve only had Google Play Services crash once. The permissions model is a lot cleaner (you can deny specific permissions to specific apps, though more on that later). In the past, when I’ve switched from an iPhone to an Android phone, I’ve instantly felt painful regressions, and they wouldn’t go away no matter how much I got used to the operating system. This time, while there are things I prefer about each system, I can’t obviously point at one and say “I strongly prefer this one”. I don’t really have any major complaints about the core Android operating system any more. But there are some things I really like about it:
  • ChromeView integration. If I open a link in the Twitter app, I can select “Open in Chrome” from the drop-down menu, and my exact screen will be preserved, scroll-location and all, just in a standalone Chrome tab. This is amazing.
  • Chrome tabs are preserved offline a lot more than I saw on my iPhone, and the “download when next online” feature is pretty swanky too.
  • Gboard (Google’s swipe keyboard) is really nice (and because it’s better system-wide supported, works in most apps, unlike on iOS). Nonetheless, every now and then an app appears to use its own custom keyboard, or something, and I find that my attempts to swipe to type fail randomly.
  • Sharing intents work a lot nicer than iOS’s half-baked copy of them.
I still don’t understand why Phone and Contacts are separate apps, or when I’m meant to use each of them.

Region-locked app-store

My one big complaint, which I’m aware is a little niche, is around region locking in the Play store. For weird historical reasons, my Google account is US-based rather than UK-based. Accordingly, I cannot install the apps for one of my banks, or one of my credit cards, and the BBC iPlayer and iPlayer Radio apps don’t let me download content. On iOS, I could switch the country in which my account is registered through an official process, by entering a billing address and card in that country, which would let me download apps from the other country, and then switch back. On Android, I can only emulate this by signing in as a second user, which is a generally poorly supported flow. For one thing, it means I need to switch accounts to get to the apps, and for another, storage doesn’t really work properly (downloads from the BBC apps fail in obscure ways, for instance).

The workaround here would be to side-load the apps, but that’s kind of sketchy security-wise.

If you want to install apps from multiple regions, I recommend an iPhone.

Permissions

  • “All files and photos”. Oh god. If you want to give an app permission to save any files or photos or data, you need to give it permission to read, write, or delete all files and/or photos on your phone. This is very alarming! Please please please let apps have a private sandbox, and a separate permission for some kind of global storage!
  • Notifications are on by default. Accordingly, my notifications are super spammy, until I manually tweak them down. This is pretty annoying; I suspect that one of the reasons that iOS is less spammy notification-wise is that there’s a user prompt before notifications can ever be sent, and so app developers need to convince people to enable notifications with some good reason before they can spam.
  • Each bank / credit card app on my phone told me it needed permission to make and manage phone calls, and promised me it wouldn’t ever actually use it, but it needed to use it to verify security somehow. Weird.

Photos

The Photos app doesn’t appear to have a way to let me scroll through all of my taken photos, received photos from WhatsApp, received photos from each other app, screenshots, etc, in one view; I need to remember which one of the above the photo I’m looking for was, and open the correct folder. Not the end of the world, but a little annoying.

Android Pay

Android Pay appears to have less broad adoption from credit card companies and banks than Apple Pay does. But I don’t really use either, so it doesn’t matter to me that much.

WiFi Connections

My iPhone was pretty sluggish to connect to wifi, and often needed prompting, particularly when on the tube. My Pixel 2 appears to join wifi networks really quickly, and without prompting, which gives a much smoother experience on the tube.

Automatic re-enabling of Wifi

I found out about this feature yesterday! If you’ve disabled wifi, and Android detects that you’re near a wifi network which it thinks is good, it will turn your wifi back on! This is a terrible feature to enable by default! Firstly, if I’ve disabled my wifi, it’s for a reason, and you shouldn’t assume to know better. Secondly, I can only assume that it does this detection by probing, which means that 1) it’s using my battery to do so, and 2) it’s broadcasting my MAC address when I may have asked it not to.

The back button

I find it pretty hard to predict what the back button will do at any time. If I open a link from an email, and press back, it closes the Chrome tab and takes me back to my email. If I click a Twitter link from Signal, and press back, it takes me to the most recent Tweet before that which I opened from a link in Signal. What?

Camera

The camera is pretty good. A similar quality to what I’d expect on an iPhone (though slightly worse in low-light conditions). It takes “motion images” by default, which are kind of fun (they give a 1-3 second animation around the frame you’re actually taking), but a little annoying to share (if I try to email them to myself, they’re about 8MB each, and there doesn’t appear to be an easy way to just send one frame).

Pixel 2 software

This is something I’m actually really disappointed about. One of the joys of getting a Google experience phone is that vendors don’t mess with the UI, bundle in un-removable apps, etc. And yet, on my homescreen I cannot move or remove the search bar at the bottom of the screen (which I never use, but frequently accidentally tap on when I’m trying to open one of my pinned apps), and I cannot move or remove the “At a glance” display at the top of the screen. I really wish I could actually configure my device as I want to, without rooting it.

Pixel 2 hardware

Weirdly, this is my area where I have the most complaints. Which I wasn’t expecting, considering the price tag and marketing of the phone.

Screen

My screen is covered in scratches. I have no idea what from; I’ve not had any incidents which should have scratched it. I’ve used it exactly the same as my iPhone; keeping it in the same pocket with the same things, putting it on my same bed-side table. I can only assume the iPhone screen is made of a tougher material, or has a coating, or something. I’m actually pretty disappointed that my phone has several noticeable scratches six weeks in.

Fingerprint sensor

I really dislike the location of the fingerprint sensor. It’s on the back, just above where your index finger rests on the back of the phone. With my iPhone, if I had the phone on my desk, I could tap the sensor with my index finger, and interact with my phone. With my Pixel 2, I need to pick up the phone, and poke the back of it; this is a surprisingly much higher friction activity to do something like glance at a message I’ve received.

I also accidentally press the fingerprint sensor a lot when my phone is in my pocket when fumbling for my phone, or wallet, or whatever, which necessitates putting in my PIN to unlock (because it hits the fingerprint attempt limit). Minor, but annoying.

Speaker location

At night, I leave my phone face-down because it has a flashy light on it. The speaker appears to be on the front of the phone. So if I want to settle down with a podcast or music or something, I can’t really hear it. My iPhone didn’t suffer from this problem, as the speaker is at the bottom.

One USB-C port is awful

My biggest wish is that my phone had either a 3.5mm headphone jack, or two USB-C ports, so that I can plug in both a charger and headphones. I have some good bluetooth headphones, but sometimes I want to use cheap earphones, so I’ve bought a bluetooth receiver (which is excellent, for the record) that I can plug my earphones into, but I need to carry it around, and charge it, and just ugh.

I looked at getting a splitter which allows you to plug both headphones and power into one end, and plugs into the USB-C port on the phone. As far as I can tell, there are several available, but most of them probably don’t work with any particular phone. Ugh.

The bundled USB-C -> 3.5mm adapter is awful

You need to jiggle it around before it actually works. And there’s no indication on the phone anywhere that headphones are plugged in, so reliably I will start playing music out of the phone speaker, and then keep jiggling the adapter until it plays through the earphones. This is simply not acceptable.

USB-C is far from ubiquitous

I bought a set of USB-C - USB-A cables, so that I can plug my phone into existing chargers, or my laptop, or whatever, to charge. Bundling one of these would have been nice.

Size

The phone is big. Bigger than I’d like. I can just about use it with one hand (I have pretty big hands). But most annoying, big without it being for the screen. About 1.3cm are added at both the top and bottom around the screen for… Black plastic. Seems annoying. I’d love my phone to just be 2.5cm shorter.

Sharing by Bluetooth

I miss Airdrop. I’ve tried sharing images over bluetooth from my phone several times, both to other phones and computers. It’s failed 100% of the time. I just email stuff now, but I’d rather not have to.