There is a well-known trick in Haskell development: when you cannot remember the name of a function, you type its signature into Hoogle and let the search engine find it. You know you want something that takes a list and returns the first matching element wrapped in a Maybe; you type (a -> Bool) -> [a] -> Maybe a and within seconds you have find, from Data.List. This is Roogle’s founding premise for Rust: the same search modality, applied to a language that makes it considerably harder to pull off.
The project exists because the Rust ecosystem has no equivalent. docs.rs hosts documentation for every published crate but searches only by name. crates.io does keyword and category matching. lib.rs, which adds quality signals and better categorization, is still fundamentally name-and-concept driven. When you know the type shape you want but not the function name, none of these help. The Vec<Option<T>> to Option<Vec<T>> transformation that exists somewhere in the iterator ecosystem, the string slice to owned string conversions, the collection combinator that takes two parameters of the same type and returns a combined one: these are findable by shape when you cannot recall names, if the tooling exists to search that way.
How Hoogle Actually Works
Understanding what Roogle is attempting requires understanding what Hoogle does under the surface.
Hoogle’s core operation is Robinson first-order unification, the same algorithm that underlies Hindley-Milner type inference. Given two type expressions, unification finds a substitution — a mapping from type variables to types — that makes both expressions identical. If you search for a -> a, Hoogle will match Int -> Int, String -> String, and any polymorphic b -> b by substituting concrete types or normalizing variable names. The algorithm walks both type trees in parallel: type applications unify by recursing into the function and argument positions; type constructors must match exactly; type variables bind to whatever is on the other side, subject to the occurs-check that prevents circular bindings.
Before attempting unification, Hoogle normalizes type variable names canonically, so x -> x, b -> b, and a -> a are treated as the same query. Typeclass constraints are handled as side-conditions. A function like sort :: Ord a => [a] -> [a] surfaces for a query of [a] -> [a]; the constraint restricts valid uses of a but does not change the structural shape. Hoogle includes the constraint in results and uses it for ranking: a more constrained signature is a less general match.
This works cleanly in Haskell because the type language is compositional without additional complexity. Type applications nest as trees: Maybe (Either Int a) is a structure that unification walks directly. There are no lifetime annotations, no borrow semantics, and typeclass constraints follow rules the unifier treats as metadata rather than as structural elements.
Roogle’s Query Model
Roogle follows the same approach. You write a Rust-like type signature and the engine finds functions with matching shapes:
(Vec<String>, usize) -> Option<String>
Vec<T> -> usize
(T, T) -> T
The engine indexes Rust function signatures by parsing rustdoc’s JSON output, which represents the public API surface of a crate. It stores each function’s parameter types and return type, normalizes type variable names, and builds an index suitable for structural matching.
Queries with type variables work: a search for Vec<T> -> usize will match Vec::len because T unifies with the element type placeholder. The matching walks the type tree and binds variables to subtrees. A query like (T, T) -> T will surface things like std::cmp::max after unifying T with its bounded parameter.
This is already useful for the common cases. Iterator transformations and collection operations have type signatures specific enough to meaningfully constrain a search, and when you are exploring a large crate for the first time, knowing you want a function that takes one of its types and produces another is often the most concrete thing you know.
Where Rust’s Type System Creates Problems
The challenge is that Rust’s type signatures carry information Haskell’s do not, and that extra information makes the unification problem harder without a clean solution at every level.
Lifetimes are the most structurally disruptive element. The function fn longest<'a>(x: &'a str, y: &'a str) -> &'a str carries lifetime annotations expressing a constraint: the output’s validity depends on both inputs remaining live. For search, a user querying (&str, &str) -> &str almost certainly wants to elide these details. Roogle’s pragmatic approach is to treat lifetime annotations as irrelevant to matching, which is the right call for usability but means the tool cannot distinguish functions with meaningfully different lifetime semantics.
Trait bounds require a more consequential approximation. In Haskell, Ord a => is a side-condition on a type variable. In Rust, bounds can be substantially more complex: F: Fn(A) -> B + Send + 'static. Standard unification treats type variables as unconstrained placeholders; bounds are checked separately. For search, a query for (T, T) -> T should match fn max<T: PartialOrd>(a: T, b: T) -> T, which is correct behavior. But ranking by constraint specificity requires scoring logic beyond basic unification: a fully generic (T, T) -> T is semantically different from one bounded to T: Clone + Debug, and the right ordering of results depends on how you weight that difference.
Associated types are where structural matching starts to break down more fundamentally. A function returning I::Item where I: Iterator does not have a concrete return type in its signature. Its return type is determined by what concrete Iterator the caller supplies. Matching queries that mention concrete types against functions with associated type returns requires reasoning about trait implementations, a separate problem from structural unification entirely. A search index that wants to answer “what functions return an iterator of u32 values?” needs to know which Iterator implementations have Item = u32, which means maintaining a separate trait-impl index and running something like trait resolution during query time.
impl Trait in return position is similarly opaque. A function returning impl Iterator<Item = u32> does not expose its concrete return type; the type is determined by the function body, not its signature. Matching against concrete types in search queries requires approximations that can produce both false positives and false negatives.
These are not failures of Roogle’s design; they reflect genuine limits of what first-order unification can accomplish against Rust’s type signatures. A complete solution for the harder cases would require something closer to Rust’s own trait solver embedded in the search engine. As of early 2026, Rust’s trait solver is itself being rewritten (the new solver has been stabilizing piece by piece on nightly), and the correctness guarantees it provides for real Rust programs are substantially more complex than what a search heuristic can easily replicate.
What rustdoc Already Does
It is worth separating Roogle’s ambitions from what already exists inside the official toolchain. In Rust 1.74 (released November 2023), rustdoc’s built-in search gained meaningful type-aware matching. The rustdoc documentation describes a query syntax for argument and return types: str -> String, usize, usize -> usize, Vec<u8> ->. Type variable names in queries are treated as wildcards, and the search does structural matching against the pre-built JavaScript index embedded in each generated documentation site.
This is genuinely useful and lives inside the standard Rust documentation workflow. When browsing std docs and wanting to find string functions that produce an owned String from a &str, you can query for it. The feature has continued to improve through subsequent releases.
But the scope is strictly intra-crate. rustdoc’s search index covers the crate whose documentation you are currently reading; it does not span the ecosystem. Roogle’s goal is the cross-crate case: a unified index across many crates that answers questions like “somewhere in the Rust ecosystem, does a function with this type shape exist?” The tooling for that specific question does not exist in production form anywhere in the official Rust infrastructure, and building it requires solving both the indexing infrastructure problem and the algorithmic problem simultaneously.
The Infrastructure Gap
Even if the algorithmic challenges were fully solved, building Hoogle-equivalent infrastructure for Rust would require something comparable to what Hackage and the Haskell community have built over twenty years: a centrally maintained index continuously updated as new crate versions are published, hosted as an interactive service, and fast enough to be useful for real development workflows.
Roogle in its current form is a local tool. You point it at a set of crates, build an index, and query it. The repository is a proof of concept rather than a hosted service, and the gap between that and an always-current ecosystem-wide search is substantial. It involves infrastructure decisions (how to run rustdoc across all of crates.io at scale, how to keep the index fresh as crates publish new versions, how to handle build failures) that are separate from the core algorithmic work.
Hackage and Hoogle reached their current state through sustained community investment over many years. Neil Mitchell’s original Hoogle from 2004 was also a local tool over a narrow index; the production infrastructure came later. The right comparison for Roogle’s current state is Hoogle in its early years: the concept is sound, the implementation demonstrates it works, and what it would take to mature into production infrastructure is reasonably clear.
What Makes This Worth Watching
Type-directed search is the right tool for a specific, real problem that Rust developers encounter regularly. The language’s type system is expressive enough that signatures carry significant semantic information, and the ecosystem is large enough that name-based discovery regularly fails for functions you know should exist. The combination of those two facts makes the Hoogle model particularly valuable in Rust, more so than it would be in a language with less informative types or a smaller ecosystem.
The interesting open question is whether the right outcome is Roogle maturing as a standalone tool or rustdoc eventually growing ecosystem-wide type search as an official feature. Both paths exist, and the algorithmic work Roogle is doing feeds either direction. For now, the project exists as an existence proof that the concept is applicable to Rust, complications and all, and as a precise map of the problems any production implementation will need to solve: not just unification, but lifetime elision, trait-bound ranking, associated type resolution, and the index infrastructure to make it all queryable at ecosystem scale.