A Hoogle for Rust


I wanted a Hoogle for Rust: a way to search for functions by their type signature instead of their name. You have an Option<T>, you want the T, and you can't remember whether it's unwrap, expect, unwrap_or, or unwrap_or_else. Type the shape Option<T> -> T and get the functions back.

The core is a small first-order unification engine. The two decisions on top of it I still think are right:

Rank, don't reject. A search tool is not a type checker, which is the mistake I kept making. Option<T> -> T should still show you a concrete Option<usize> -> usize, just ranked below a generic one. Only genuine nonsense gets rejected.

Two kinds of hole. A type variable like T is a claim that something is generic, and it gets scored down, not thrown out, when it ends up pinned to a concrete type. A wildcard ?F means "I don't care what goes here" and matches anything for free.

The wildcard is where Rust got interesting. Haskell's Hoogle barely thinks about wrappers. In Rust the same operation is smeared across T, &T, &mut T, Box<T>, Rc<T>, Arc<T>, and whatever you wrapped it in last. So ?F<T> means "T is in there somewhere, inside some wrapper, and I won't say which":

?F<T> -> T
matches:  Vec<T> -> T
          Option<T> -> T
          Arc<Vec<Option<T>>> -> T
          Result<T, E> -> T

Same wrapper everywhere it appears, non-empty, shallower matches ranked higher. Once the hole can sit anywhere there's no single answer, so matching becomes a small bounded search over where the hole could go. That part I'd put on a slide. It was also not enough.

Why I stopped

Building it is how I found out it wasn't worth building.

Rust types say less than Haskell types. A Haskell signature is almost a specification. a -> a is basically id and the type tells you so. A Rust signature is the arguments, the return type, and a shrug; the real meaning lives in traits, bounds, and mutation, none of which are in the shape you search. The whole ?F contraption is me fighting a symptom of that.

The data I'd need to do it properly lives behind nightly. Real trait resolution, canonical paths, macro-expanded items: that's rustdoc's JSON output, which is nightly-only and an explicitly unstable format. "First, switch to nightly" is not something I can ask of someone who just wants to look up a function. On stable you parse source with syn, which means no cross-crate resolution, nothing from macros, and no trait solving.

And the feature already ships, in the editor I was building it in. rust-analyzer autocomplete: put a . after a value and get every method that applies, filtered by what's in scope. That's most of the use case, already installed.

What's left is maintenance, in exchange for being slightly worse than two tools I already have.

The code is in a tarball. The interesting part is the matcher in unify.rs. If you want to push the tool, the cheap wins are all on stable: parse trait definitions (that's where the Iterator combinators hide), capture doc comments, and scope the index to one project's dependencies. If you're willing to require nightly, there's a genuinely good tool in here. I wasn't.

Good excuse to write a unifier from scratch. Time to go find a different yak.