## Homework 4: Rust and Type Inference

**Due date:** December 1, 6pm

**Grading**: 15% (CS 345H) or 9% (CS 386L) of your course grade

In this homework we'll implement type checking and type inference for the simply typed lambda calculus. Unlike the previous homeworks, this one has no proofs and therefore no Coq. Instead, we'll implement our type inference in Rust, which also gives us a chance to learn more about Rust's type system.

### Table of contents

### Preparation

Since we'll be writing Rust for this homework,
you'll want to make sure you have a development environment set up for it.
Start by installing Rust itself;
the standard way to do this is by installing `rustup`

,
which should give you the right instructions for your platform.
There are more detailed instructions in Chapter 1 of the Rust book.

You'll also want an editor.
As with the previous homeworks,
I suggest using Visual Studio Code,
as it has a nice Rust integration called `rust-analyzer`

.
To get that Rust support,
click the Install button on its homepage
or search for `rust-analyzer`

in VSCode's extension pane.

### Get the code

We'll be using GitHub Classroom to check out and submit this homework.
Follow the GitHub Classroom URL on Ed to create your private copy of the homework repository,
and then clone that repository to your machine.
For example, the repository it created for me is called `hw4-jamesbornholt`

, so I would do:

```
git clone git@github.com:utcs345h/hw4-jamesbornholt.git
cd hw4-jamesbornholt
```

To check your Rust setup is working correctly, run this command from your homework directory:

```
cargo test
```

You should see a bunch of failing tests. You'll know you've completed the homework when all these tests pass!

### Complete the homework

This homework is in two parts.
The homework directory is a workspace,
a collection of two Rust crates called `warmup`

and `lambda`

. There are homework problems in both crates.
There are a total of 64 points available on this homework.

#### Part 1: Warmup (12 points, problems 1-4)

This part is just a few small warmup problems to see Rust ideas that will be useful in Part 2.
Complete the four `todo!()`

s in the `warmup/src/lib.rs`

file. You can test your solutions by running:

```
cargo test -p warmup
```

#### Part 2: Lambda calculus

In this part, we'll implement type checking and type inference for the simply typed lambda calculus (STLC).
I recommend starting by reading `lambda/src/lang.rs`

, which is where we've defined the syntax of
STLC and its type system. You must not modify this file.

We've also included a small REPL to interact with the type checker and type inference engine you'll build. Run it like this:

```
cargo run
```

The REPL has three commands: `#parse`

to print the parsed form of a lambda calculus term,
`#check`

to run your type checker on a term,
and `#infer`

to run your type inference engine on a term.
Each command takes a single lambda calculus term as input.
You can type `\`

to mean `λ`

.
For example:

```
λ> #parse \x:bool. x
Abs(Var("x"), Bool, Var(Var("x")))
λ> #check \x:bool. x
(bool -> bool)
λ> #infer \x. x
('0 -> '0)
```

Here, `'0`

is the pretty-printed form of a *type variable*.

#### Part 2a: Type checking (18 points, problem 5)

In this part we'll implement a type checker for terms in STLC. The type checker takes as input a term and returns a type if the term is well-typed, or an error otherwise. That might not quite conform to your notion of a "type checker", but the trick is that the STLC type system is syntax-directed -- at most one typing judgment rule applies to any given term. That means we can construct the required type just by looking at the term, so we don't need to be given the type as an input.

Complete the `todo!()`

in the file `lambda/src/check.rs`

. There are tests for this part in the file
`lambda/tests/check.rs`

. You can run those tests like this:

```
cargo test --test check
```

#### Part 2b: Type inference (34 points, problems 6-11)

In this part we'll implement type inference,
which takes a term and returns a type that makes the term well-typed if one exists.
The difference between type inference and the type checker from the previous part is
that the inference engine needs to be able to *invent new types*. For example, given this term:

```
λx:T0. (x true)
```

a type checker would fail, because the type variable `T0`

is not a function and so cannot be
applied. A type *inference* engine, on the other hand, would realize that `T0`

must be a function
for this program to be well typed, and that function must take a Bool as input. It would
therefore return the type:

```
Bool -> T1 -> T1
```

The particular type inference algorithm you're going to implement is known as *Hindley-Milner
(HM) type inference*, named for J. Roger Hindley and Robin Milner. It's a very popular type
inference algorithm, and extensions of it are used in almost every language that does type
inference, but notably ML (and SML and OCaml) and Haskell. We're going to implement a slightly
restricted version of HM that does not support parametric polymorphism.

Hindley-Milner type inference works in two phases. The overall idea is to generate a set of
*constraints* over type variables that must be satisfied in order for the term to be well typed, and
then solve those constraints via *unification* to find a concrete type that works. You'll be
implementing both these phases. Hindley-Milner type inference is also sometimes called
"constraint-based typing", because of the two-phase approach of first generating constraints and
then solving them.

Implement Hindley-Milner type inference by completing the `todo!()`

s in the file
`lambda/src/infer.rs`

.

**Notes**. This is hard! Start by carefully reading the long comment at the top of that file, which describes
the type inference algorithm. We've provided the skeleton for you to complete; you shouldn't need to
define any more methods, and every method we've defined should likely be used in your solution
(they're all used in ours). You should also read Chapter 22 of Types and Programming Languages,
which is included in your homework directory as `types-and-programming-languages-ch22.pdf`

.
Section 22.3 covers constraint-based typing, and section 22.4 covers unification.

Also, the problems in `lambda/src/infer.rs`

aren't in order—you likely want to tackle some of the
later problems first, as they provide utility functions you'll need for the two big problems (6 and 7).

**Testing**. There are two sets of tests for this part. The first set test the two phases of Hindley-Milner in
isolation, so you can test the two parts of your solution independently. These tests are in the same
`lambda/src/infer.rs`

file as your solution will be in, and you can run them like this (be careful of
the space after the second `--`

):

```
cargo test --lib -- phase_tests
```

The second set of tests run your entire type inference implementation. These tests are in the file
`lambda/tests/infer.rs`

and can be run like this:

```
cargo test --test infer
```

### Resources for writing Rust

Rust is a mainstream programming language, which means the documentation is very good and there's ample resources on the internet to help learn it. It's somewhat infamous for having a steep learning curve, although we've tried to write the homework in a way that will keep you away from the sharper edges. The official Rust book is exceptionally good and should be your first port of call for questions about Rust. The standard library documentation is also very good and is a great way to track down methods that can do something helpful for you.

### What to submit

Submit your solutions by committing your changes in Git and pushing them to the private repository GitHub Classroom created for you in the Get the code step.

The only files you should edit are:

`warmup/src/lib.rs`

`lambda/src/check.rs`

`lambda/src/infer.rs`

GitHub Classroom will automatically select your most recent pushed commit
*before the deadline* as your submission.
There's no need to manually submit anything else via Canvas or GitHub.

GitHub Classroom also has a simple autograder that just runs all the tests using GitHub Actions.
This is only a partial grader, and we're using it only to give you early feedback on your submissions—we will still be reading and grading your code by hand.
Just because the autograder passes doesn't mean you'll get full points; just because the autograder fails doesn't mean you *won't* get full points.