Swift Compiler Bugs

Swift is a great language, and far more enjoyable to program in than Objective-C, but it is still in its infancy, and bugs abound. One of the more frustrating is compiler errors that are flat-out wrong. Take a look at this (in Xcode 6.1):

Could not find member

“Could not find member ‘date’?” Trust me when I tell you that date is indeed a member of a Todo object. So what’s going on? Let’s tweak it a bit:

Cannot invoke

Ah, there we go. Turns out you can’t actually compare dates like that (hmm…I smell a follow-up post on writing extensions). Here’s how you do it:

Fixed

Here’s another one. The following is essentially a no-op:

for i in 0..<0 {
    println(i)
}

That’s just a for loop from zero up to, but not including, zero. And this code is fine, doing what it is supposed to do—nothing.

However, try making the ending value of the loop less than the starting value. This should be equivalent to the above:

for i in 0...(-1) {
    println(i)
}

The ... operator means “up to and including”. So this should also be a no-op (and it is in most programming languages). But:

For Loop Boom

Even worse, it will actually crash on the first executable line of code:

For Loop Boom 2

None of this should scare you away from using Swift. It’s a fun language to work with, and ends up being much more concise than Objective-C. Just keep in mind that there are still bugs to be worked out, and if you’re getting errors that just make no sense, there’s a fair chance you’ve hit one.

The Importance of Sucking

One of the defining charactaristics of a successful programmer is being OK with the feeling that you have no idea what you’re doing. I’ve been programming for over 30 years and it still happens to me regularly. The key is pushing on through that dark period, trusting that eventually you’ll catch a glimmer of light and it will slowly start to make sense.

There are a few tricks I’ve picked up to help me get over that initial hump. The first is to find a tutorial someone has written, and just follow along, just to get something working. Once I have something working, no matter how basic, not only do I start to get a better sense of how all the pieces fit together, but I can start tweaking it and learn from how it responds.

Can’t find a good tutorial? Great! Here’s your opportunity to give back, and write your own. There’s really no better way to learn. Start with doing the most basic thing imaginable. Just get something working. Write down your steps as you go. Then add to it, write it down, repeat. You’ll eventually have something coherent that you can post, and you’ll earn a ton of internet karma points.

It also really helps if you have a specific project to work on, otherwise the tutorial can be a bit of a dead end, and your effort can feel purposeless. Don’t have a project, and can’t think of one? Rewrite something you’ve already written. Ask friends and family if there’s an app they wish they had. Write a basic version of an app that you’re too cheap to buy. Write a todo app.

Start small, though; the bigger the project, the less likely you are to finish it. Minimum viable product (MVP) isn’t just Silicon Valley jargon; it applies equally well to personal projects. Do the simplest, dumbest thing possible. It’s easy to iterate and improve once you have something working, and you can always throw it all away and rewrite it once you feel you know what you’re doing.

The feeling of sucking at something and being completely lost is a part of learning. Everyone starts out that way. If you’re actively avoiding sucking, you’re not learning. Get lost. Seriously, find someplace to get yourself lost. You’ll be better for it. Then tell us about it when you inevitably emerge from the darkness.

Prose.io

Prose.io is a content editor for GitHub. It supports Jekyll sites (like this one), and provides Markdown previews. I’m writing this post using it.

I’m sure I looked at Prose some time ago, but must have decided something was lacking. But I just took a look at it again, and it’s just what I need. In fact, I had started a similar project myself not long ago, and now I feel relieved that I can officially call it dead and stop fretting about having stalled out on it.

The Future of Programming

The most dangerous thought that you can have as a creative person is to think that you know what you’re doing. Because once you think you know what you’re doing, you stop looking around for other ways of doing things.

Brett Victor, The Future of Programming

He’s posted references and follow-up notes at http://worrydream.com/dbx/.

Why is Git Rejecting Me?

Every Git user, from novice to expert, gets this error message at some point, maybe even daily:

$ git push origin main
To [email protected]:bgreenlee/myrepo.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '[email protected]:bgreenlee/myrepo.git'
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and merge the remote changes
hint: (e.g. 'git pull') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

There are a couple ways this can happen, but it all boils down to this: the remote repository you’re pushing to has one or more commits that you don’t have in your local repository. Don’t worry, though, the fix is easy, although how to go about it depends on what you’re trying to do.

Refresher: Rebase vs. Merge

Before we dive in, a quick refresher on rebase vs. merge. These are two general strategies for combining two branches.

Consider the following case:

  main: A -> B -> C
my-fix:       \-> D

Here, you created a branch named “my-fix” from main while it was at commit B. While you were working on your fix, someone else pushed commit C to main. You finish your fix and commit it to the my-fix branch. That’s commit D above.

Now you’re ready to merge your my-fix branch back into main. There are two ways to go about this.

Merge

You can do:

git checkout main
git merge my-fix

This will resolve the differences between the two branches by doing a three-way merge using commits C and D and their common ancestor, B. It does this by creating an additional merge commit, which we’ll call M:

main: A -> B -> C -> M(B,C,D)

Rebase

You can also do:

git checkout my-fix
git rebase main
git checkout main
git merge my-fix

The first checkout is just to make sure you’re starting on the right branch. You may already be on the my-fix branch, in which case you can skip it. What git rebase main does is “rewind” your branch to the point at which it diverged from main, B, then apply all the commits from main made after that point (C), then “replay” your commits to my-fix (D). What you get is:

my-fix: A -> B -> C -> D’

Note that I used D’ above. This is because while the content of the commit is the same as your original commit D, it’s technically a different commit, with a different SHA. This will be important later.

The last two commands switch back to main and then merge my-fix with main. Since at this point my-fix has everything that main has, plus one extra commit, the merge is just a “fast-forward”, meaning that all git has to do is move the HEAD pointer to D’—no merge commit is required.

Now, merging vs. rebasing is somewhat of a religious issue, although in my experience, most organizations prefer rebasing, as it keeps the commit history from getting cluttered with merge commits. I am going to go on the assumption that rebasing is the preferred method.

Now, let’s get back to fixing our problem.

Scenario 1: Pull, then Push

This is the most common scenario, and simplest fix. Say you’ve made some changes in the main branch of your local repository, then go to push them to the main branch of the remote repository. If your push is rejected, what has most likey happened is that someone else pushed some changes to the remote main while you were making your changes, and you need to pull them down to your repo before you can push your changes up. So do a ‘git pull –rebase’, then push again.

Scenario 2: Force Push

Let’s say you’ve been working on your own branch, and suddenly git is rejecting you. Now you know that no one else has been pushing commits to that branch, so what happened?

Most likely, you updated your local branch by rebasing against main. Again, this “rewinds” the commits in your branch to the point where they diverged from main, then pulls in the commits from main that you don’t have, then “replays” the commits in your branch that weren’t in main as new commits. So when you go to push those to your remote branch, even though it may look like the commits are the same, the SHAs are different, so git will refuse to update the remote branch.

One way around this is to force push:

git push -f origin my-fix

This tells git that you don’t care about what’s on the remote branch, just update it with what you have locally. This can be dangerous. Don’t do it unless you know what you’re doing. You would only ever want to do this if you are sure that you are the only one working on that branch, and that you have all the commits locally that are on the remote branch (i.e. you didn’t push changes from another machine). If that’s not the case, and, say, a coworker had pushed some of her own changes to your branch, you will overwrite those changes. Now, chances are they will be recoverable (she probably still has them in her local repository), but digging yourself out will be painful.

If you are in a situation where you’ve rebased your branch and meanwhile your colleagues have pushed changes to the remote branch, one way to get back on track is to cherry-pick their commits into your branch, and then force-push your branch (after telling your colleagues to hold off on pushing):

git fetch  # to make sure your repo knows about their commits
git log origin/my-fix  # to see what their commits are, and copy the shas
git cherry-pick <sha1>
git cherry-pick <sha2>
git push -f origin my-fix

Your colleagues will then have to delete their local branch and pull it down again.

I’m sure I’ll get some hate mail for suggesting force push as a viable option, so I’ll reiterate: don’t do this unless you know what you’re doing. Even if you do know what you’re doing, you’re playing with fire, as a developer at my company learned when he accidentally force-pushed to origin main, which brought most of the engineering team to a halt while we figured out what got blown away and reconstructed main. (To git’s credit, it is hard to do real irreversible damage; between the reflog and commits stored in your colleagues’ repos, you can almost always recover. It just may take some time to sort things out.)

I should also note that it is possible to disable force-pushing on a per-repository basis (unfortunately not per-branch, AFAIK, but there are hoops you can jump through).