MonstroBlog

Learning a New Programming Language

I’ve learned a lot of languages over the years. Before the Internet that meant buying a book and working through it, often without even being able to run programs in the language. That’s how I learned C, from the original version of Kernighan and Ritchie’s The C Programming Language, long before I had access to an actual implementation, which in those days basically meant a computer running Unix.

When I got interested in Scheme, I bought PC-Scheme a version of the language for IBM PC’s written (for some reason) by Texas Instruments. I ran this on my father’s IBM PC-XT, and worked through the entirety of Structure and Interpretation of Computer Programs using it. (In between sessions of the original SimCity.)

Fast forward nearly thirty years, and I recently read the article “Why I Am Learning Rust Now”, which made a pretty good case for Rust as a modern systems programming language. It strives to be efficient, like C, yet incorporate modern features for type safety, concurrency support, functional programming, etc. In exploring Rust, I found that learning programming languages seems to have become a rather tedious process for me. Why isn’t it as challenging and fun as it was when I learned Scheme?

Things have changed a lot since learning a new programming language meant a trip to a distant book store and spending a fair amount of money on a book, then trying to figure out how you would actually obtain an implementation of the language.

Nowadays, you can download an implementation of pretty much any language for free – in my case this was simple as brew install rust. Then, rather than buying an expensive text-book, you just look for online books, tutorials, or whatever, and get to work.

I followed this path when I went to learn Rust. On the Rust home page I found a link to The Rust Programming Language and started working through it. This more or less mimics the way I used to learn new languages. Of course it is easier now: if I don’t like one book (or tutorial) I simply google around until I find another one. Also, I almost always have a working implementation of the language compiler or interpreter to play with as I read.

The link to “The Book”, as they called it, said that when I finished working through it I would be an intermediate Rust developer. It starts by explaining how to obtain and install the rust compiler. Then the obligatory “hello, world” program, including how to compile and run using the cargo command.

The next chapter was actually fairly nice, that a lot of these sorts of primary language descriptions lack: a tutorial. This one involved a very simple guess the number game. Nothing fancy, but it gave a bit more detail on cargo, showed how to include library modules, and actually had me programming. Nice.

But after that promising start, “The Book” began to look like almost every other language’s big book. We entered the great catalog of features. Here is how this language’s syntax differs ever so slightly from all the others. Here are our loop constructs. And so on until every feature of the language has been introduced. This is barely one step up from a reference manual.

For Rust, most of this information (and most of the book) is in Chapter 4, “Syntax and Semantics”. By the time I got to section 23, “Closures”, my eyes had glazed over. I had already forgotten most of what I had learned in section 8, 9, and 10: “Ownership”, “References and Borrowing”, and “Lifetimes”.

Now this business about ownership, borrowing and lifetimes are the keys to understanding how Rust tries to provide memory safety at compile-time rather than at run-time. This is a major feature of the language. It seemed like a big deal compared to C or C++, so I was a little worried when I realized I wasn’t very clear on how it worked. I was getting lost in detail. I knew I wanted to find a different way of learning this language.

Recently, I’ve spent some time learning (or at least learning about) web development. Here the emphasis seemed to be on learning by doing. The ubiquitous hello world program is replaced by the ubiquitous to-do list app. In writing a more complete app, you are introduced to a lot more of then language. While it can degenerate into simply cutting and pasting from the tutorial to your editor, at its best it encourages you to experiment, add your own features and really learn the language.

Now the to-do list app has actually become such a staple of these tutorials that an amusing blog post has been written claiming that “Every Time You Build a To-Do List App a Puppy Dies”. This post includes a list of other projects you could implement instead the next time you want to learn a new web framework.

And that list has a lot to do with my new plan for trying to learn Rust. I didn’t immediately go from that post to my plan, but I suspect it was floating around in the back of my mind. I was also remembering how I had learned Scheme by solving a bunch of interesting programming problems from a text book. And, I was fresh off looking at smaller, but still interesting programming problems in the context of screening applicants for work (see my previous post about HackerRank).

Instead of reading about Rust, I wanted to use it. Maybe I could find small programming tasks that I could complete in Rust and learn the language that way. A bit of Googling led me to a GitHub repository called Mega Project List that had a bunch of projects of about the right size and difficulty. I went down the list and picked out:

Complex Number Algebra - Show addition, multiplication, negation, and inversion of complex numbers in separate functions. (Subtraction and division operations can be made with pairs of these operations.) Print the results for each operation tested.

That seemed like fun, and I started implementing it. I had learned enough Rust to write

1
2
3
4
5
6
struct Complex {
r: f64,
i: f64
}
const I: Complex = Complex{ r: 0., i: 1. };

Now it wasn’t my original intention to be able to use operators with my Complex type, but I had I, wouldn’t be nice if I could just:

1
2
3
4
fn main() {
let x = 1. + I;
println!("{}", x);
}

So I did, and I got the error message:

1
2
3
4
5
6
error[E0277]: the trait bound `{float}: std::ops::Add<Complex>` is not satisfied
--> main.rs:168:13
|
168 | let x = 1. + I;
| ^^^^^^ the trait `std::ops::Add<Complex>` is not implemented for `{float}`
|

Well, I still didn’t know how to overload operators, in Rust, but I did now how to implement a trait, and It wasn’t hard at all to find out what needed to be implemented for this trait. So I added this:

1
2
3
4
5
6
7
8
9
impl ops::Add<Complex> for f64 {
type Output = Complex;
fn add(self, other: Complex) -> Complex {
Complex {
r: self + other.r,
i: other.i,
}
}
}

I had to do a little bit more to get print to work. Implementing fmt::Display did the trick. Now my little program ran. I didn’t have to implement any sort of constructor for complex numbers. I just created an I object and used the add operator to do the rest. That was completely accidental, but very pleasing.

Now I went on and created my other operations, and made sure I could use them on any combination of Complex and f64 and I was done. A few more test cases in main to prove it worked and I was done.1

I hadn’t intended to, but I learned how to implement operator overloading in Rust. I got some practice actually using the language to solve a problem, and I had a fun morning.

Now the only question is which project should I tackle next?


  1. If your curious, I’ve checked the whole program into GitHub here. I don’t claim it is anything special, but it was was a lot more fun than reading through the documentation and only kind of understanding how to do operator overloading.