Searching Rust by Type Signature: What Roogle Gets Right About API Discovery
Source: lobsters
There is a class of problem that comes up regularly when working with a language that has a rich type system: you know exactly what types you are working with, and you have a rough sense of the transformation you need, but you cannot remember whether the standard library or a popular crate already has a function for it. The function probably exists. You just cannot find it because you do not know its name.
This is precisely the problem Roogle is built to solve. It is a search engine for the Rust API surface that lets you query by type signature rather than by identifier. Instead of typing collect into a search box and hoping, you describe the shape of the function you want.
Where the Idea Comes From
Roogle is explicitly modeled on Hoogle, which has been a fixture of the Haskell ecosystem for over a decade. Hoogle lets you search Haskell libraries using type signatures. You want a function that maps over a list? Search for (a -> b) -> [a] -> [b] and Hoogle surfaces map along with everything else that fits that shape. You want something that takes two lists and produces a list? Search [a] -> [b] -> [(a, b)] and you find zip.
What makes Hoogle work is that Haskell’s type signatures are unusually information-dense. Because Haskell is purely functional and has a strong, expressive type system with parametric polymorphism, the signature of a function tells you a great deal about what it can possibly do. Philip Wadler’s concept of “theorems for free” formalizes this: a function of type a -> a can only be the identity function, because it cannot construct a value of an arbitrary type from nothing. The type constrains the implementation space so tightly that searching by type is often searching by behavior.
Rust is not Haskell. It has mutable references, lifetimes, trait bounds, and a fundamentally different execution model. But its type system is expressive enough that the same core insight applies: if you know the types involved in a transformation, you know a lot about the function you are looking for.
What a Roogle Query Looks Like
Roogle queries follow Rust’s own type syntax. You express the function signature you are looking for, and Roogle searches its index for functions that unify with that signature.
Some examples of the kind of queries this enables:
(String) -> &str
This finds functions that convert an owned String into a borrowed string slice, surfacing things like String::as_str and String::deref.
(Vec<T>) -> usize
This finds functions that return a count or length from a vector, which gets you Vec::len, Vec::capacity, and similar.
(T) -> Option<T>
This finds functions that wrap a value in an Option, which is useful when you’re looking for fallible constructors or conditional wrapping utilities.
The key is type unification. When you write T in a query, Roogle treats it as a type variable that can match any concrete type. A query for (Vec<T>) -> usize will match fn len(&self) -> usize on Vec<String>, Vec<u32>, or any other Vec. This is the same unification that Haskell’s type checker uses to determine whether two types are compatible.
The Technical Challenge: Rust Is Not Haskell
Implementing Hoogle-style search for Rust is significantly harder than doing it for Haskell, for a few concrete reasons.
First, Rust’s type system includes lifetimes. A function signature like fn foo<'a>(s: &'a str) -> &'a str is meaningfully different from fn foo(s: &str) -> String, and a search engine needs to handle both in a way that is useful without requiring users to specify lifetime parameters in queries.
Second, Rust has trait bounds that constrain type variables. The signature fn bar<T: Display>(t: T) -> String is not the same as fn bar<T>(t: T) -> String. When you search for (T) -> String, you might want both, or you might specifically want the version that works for any T. The unification algorithm needs a coherent policy here.
Third, Rust distinguishes between methods on concrete types, methods on generic types, and free functions, which affects how signatures appear in the documentation JSON that Roogle indexes.
Roogle ingests the JSON output from rustdoc --output-format json, which provides a structured description of every public item in a crate. This is the same data format that tools like rust-analyzer and docs.rs work with. From this, Roogle builds an index of type signatures that it can search against using a type unification algorithm that handles Rust’s specific semantics.
Rustdoc Already Has Type Search Now
It is worth being direct about the competitive context. Since Rust 1.73 (released October 2023), rustdoc’s built-in search has supported type-based queries using -> syntax. If you open any local rustdoc output or use docs.rs, you can search for String -> &str in the search box and get type-matched results.
This built-in support is significant. It works offline, it covers whatever crates you have locally documented, and it does not require a separate service. The implementation uses a similar unification approach, matching type variables and ranking by specificity.
So what does Roogle offer that rustdoc search does not? A few things. Roogle operates as a centralized search engine that can index crates.io at scale rather than just the crates you happen to have installed. It can surface matches from crates you have never heard of. It also provides a standalone interface and API that can be integrated into other tools without depending on rustdoc’s HTML output.
The relationship between Roogle and rustdoc’s own type search is similar to the relationship between a specialized search engine and a browser’s built-in search: they overlap considerably, but each has a niche. Roogle’s niche is discovery across the broader ecosystem.
Why Type-Based Search Fits Systems Programming
The case for type-based search is actually stronger in systems languages than in scripting languages, for reasons that are not immediately obvious.
In a dynamically typed language, functions are often polymorphic in ways that the type signature cannot express, and the signature itself does not exist at the language level. There is nothing to index. In a statically typed language, especially one with parametric polymorphism and strict type checking, the signature is a precise, machine-readable specification of what the function accepts and returns.
In Rust specifically, the ownership system means that signatures carry additional semantic weight. The difference between fn process(s: String) and fn process(s: &str) is not just notation; it describes a fundamentally different contract with the caller. A search engine that understands these distinctions surfaces results that are actually appropriate for the caller’s context.
For a developer who is productive in Rust but does not have every corner of the standard library memorized, the ability to ask “what takes a PathBuf and gives me a &Path” and get a direct answer is genuinely useful. The name of the function is less important than its signature when you are in that exploratory mode.
The Broader Pattern
Roogle is part of a wider trend of bringing semantic search to strongly-typed ecosystems. Similar efforts have appeared in other communities: Hoogle4Scala attempted something comparable for Scala, and Elm’s package documentation has long included type-aware search. The constraint in each case is the same: you need a type system expressive enough that signatures are meaningful search targets, and you need a way to index and query them efficiently.
Rust clears both bars. The type system is expressive, the rustdoc --output-format json flag provides a reliable indexing target, and the community has demonstrated appetite for this kind of tooling both through projects like Roogle and through the rustdoc team adding it to the first-party search.
The interesting open question is how far type-based search can go. Trait bounds, associated types, and impl Trait in return position all add nuance that naive unification misses. A function returning impl Iterator<Item = u8> and a function returning Vec<u8> are not the same thing, but a user searching for a byte sequence producer might want both. Getting the ranking right in these cases is a harder problem than the basic unification, and it is where projects like Roogle have room to experiment in ways that a first-party tool with conservative defaults cannot easily explore.