a points to b and b points back to a. If you try to print a, it will print its contents, then go on to print b. Which in turn prints the wrapped value of b, and goes on to print a. Hence, infinite recursion happens.
This is a quite confusing feature of not-so-recent Rust. You can match on a reference-typed expression (self here) and then pretend it's not a reference (you can write Cons and Nil in the match arms instead of &Cons and &Nil), but then the inner values are implicitly turned into references again. This is called "pattern matching ergonomics", you can read more about it here.
If a and b mutually point to each other, then they both have a reference count of 2: one from the actual variables that you are holding, and another from mutually referencing each other. When you drop the variables, both reference counts drop to 1, not to 0, hence, nothing is deallocated.
Because that's how it's implemented in std. Probably the reasoning is that it's conceptually a wrapper type, and you are only interested in the wrapped value rather than the full borrow state.