Go's deadcode tool follows reachable types through interface calls
The deadcode command finds functions unreachable from executable entry points. Rapid Type Analysis explains why a method can be unused even when it satisfies an interface that the program calls.
A function can have a caller and still be unreachable. Its caller may itself never run, including when that caller is a method satisfying an interface used elsewhere. Searching for references then gives an incomplete answer about whether the function is needed. Go's December 12 introduction of deadcode describes a tool that starts at executable entry points and follows the code that can actually become reachable.
Alan Donovan explains the motivation through work on gopls. Refactoring required preserving behavior for existing callers, only for the maintainers to discover that a caller was itself unreachable. Identifying that before the refactoring would have reduced the work. The account illustrates a maintenance problem even when the compiler accepts every declaration.
I like Go code that a new reader can enter without reconstructing unnecessary abstractions. Removing unreachable functions supports that aim, but deletion needs evidence about the program. A declaration with no direct call can still participate in dynamic dispatch, while a declaration with a visible caller can belong to an entirely unreachable part of the program.
Reachable implementations differ from possible implementations
The article's example defines an interface with a greeting method and two concrete types implementing it. Its main function creates only one of those types and invokes the method through the interface. The other implementation exists and type-checks, but the execution has no way to obtain a value of that concrete type.
That distinction lets the tool report the unused method and the helper it calls. A syntactic search sees an interface invocation and multiple matching methods. Whole-program analysis can connect the invocation with the concrete types created by reachable code. Matching a method signature does not by itself make the implementation reachable.
This is especially relevant when a refactoring leaves behind an old implementation. The type may still satisfy every interface assertion. Its methods may still call helpers, so those helpers have references. Yet if the program no longer constructs or exposes that implementation through reachable execution, preserving it can add maintenance work without preserving actual behavior.
Rapid Type Analysis grows a reachable set
The command loads and type-checks selected packages, then builds an intermediate representation. Its Rapid Type Analysis begins with each main package's entry points, including main and package initialization. From those roots, it examines reachable function bodies for direct calls and dynamic calls, alongside concrete types converted to interfaces.
A direct call makes its target reachable. Interface calls need more information, because several concrete methods might satisfy the interface. The algorithm combines known dynamic call sites with the concrete types encountered in reachable code. Compatible pairs add possible target methods to the reachable set.
New reachable methods can contain further calls and interface conversions. The analysis consequently repeats until no additional reachable functions appear. The RTA package's versioned description calls this a fixed point. Reachability depends on the combined propagation of call sites and runtime types, rather than one pass over declaration names.
Function values receive related treatment. The RTA description connects known address-taken functions with dynamic calls of the same type. That handles a function called through a value without requiring a direct call expression naming it at the invocation site.
The point is to approximate possible execution, not to count source references. The analysis can retain a method because reachable code can supply its receiver type. It can also identify a method as dead despite another declaration referring to it, when that declaration lies outside the reachable set.
The tool can explain why code remains live
The introduction documents -whylive for requesting an explanation of a reachable function. The output gives a chain of calls starting at an entry point, including dynamic and static steps. This makes the tool useful when a function looks obsolete but the analysis still considers it needed.
An explanation has a different role from the list of dead functions. The dead list identifies candidates for removal under the analysis assumptions. A live chain identifies the relationship that prevents another candidate from receiving that classification. It gives a maintainer a concrete path to inspect during restructuring.
I prefer an analysis that can expose that path because disagreement becomes actionable. A live result may reveal an initializer or interface conversion that a reader overlooked. The maintenance question can then concern that specific relationship, instead of an argument about whether a name appears to be used.
The command also supports JSON and template-based output for consumers that need something beyond the terminal format. That allows the analysis result to participate in another tool without turning its human-readable listing into an undocumented parsing interface.
Tests provide roots for library analysis
RTA is a whole-program analysis. A library alone does not supply a main function from which every possible downstream use can be discovered. The article therefore describes -test, which includes the generated main functions associated with Go tests.
The resulting question is whether the tests can reach a function. A library function reported dead in that analysis may be an API used by an external consumer. The finding can indicate missing test coverage, rather than justify deleting the API. The chosen roots determine what unreachable means.
This is distinct from execution coverage. The analysis considers possible reachability from the test programs. It does not observe whether a particular test run executed the function or asserted the intended result. Static reachability and measured coverage provide different evidence about the tests.
The -filter flag restricts which packages appear in output. Filtering can make a library-focused report readable while the analysis still has program roots. It should not be confused with a declaration that external callers do not exist.
The soundness boundary includes non-Go calls
The article describes the treatment of dynamic calls and reflection as conservative. A function classified dead cannot be called through those modeled mechanisms, though some functions retained as live may in fact never run. The analysis accepts that imprecision to avoid incorrect deletion advice in that part of the model.
Calls from code outside Go introduce a documented exception. The tool cannot see functions called exclusively from assembly, and it does not understand aliasing introduced by go:linkname. Its analysis is unsound with respect to those mechanisms, so such calls require separate knowledge before deletion.
For ordinary application code within the model, the tool supplies a useful answer about what the executable can reach. Its output becomes most informative when the maintainer keeps the entry points and exceptional calling mechanisms attached to the finding. That makes it possible to remove an obsolete implementation together with its helpers, instead of preserving the helpers merely because the obsolete methods still reference them.