Cobra — Interfaces#
Interface catalog#
SliceValue#
- Package:
github.com/spf13/cobra - File:
completions.go:311 - Methods:
GetSlice() []string - Purpose: Detects whether a pflag
Valueimplementation accepts multiple values (slices/arrays), allowing the completion engine to offer the same flag multiple times during tab completion. Declared as “a reduced version of pflag.SliceValue.” - Implementations: Any pflag flag type that wraps a slice (e.g.,
pflag.StringSlice,pflag.IntSlice). Cobra does not implement it; it only uses it as a detection interface via type assertion (flag.Value.(SliceValue)). External types can satisfy it by implementingGetSlice() []string. - Design quality: Single-method, perfectly segregated. Follows ISP strictly. Defined by the consumer (cobra completion engine), not by pflag — a textbook example of “accept interfaces, return structs” where cobra owns the minimal abstraction it needs without importing pflag’s wider interface.
PositionalArgs (function type)#
- Package:
github.com/spf13/cobra - File:
args.go:22 - Declaration:
type PositionalArgs func(cmd *Command, args []string) error - Purpose: Contract for positional argument validation. A command’s
Argsfield holds onePositionalArgsvalue; the execution engine calls it after flag parsing to validate the non-flag arguments beforeRunis invoked. Functions satisfying this type can be composed viaMatchAll. - Implementations:
NoArgs,ArbitraryArgs,OnlyValidArgs(top-level functions satisfying the type directly);MinimumNArgs,MaximumNArgs,ExactArgs,RangeArgs(factory functions returning closures);MatchAll(combinator). Consumer applications may also provide inline closures. - Design quality: Using a named function type rather than a single-method interface is a deliberate ergonomic choice: it permits anonymous inline closures, eliminates the need for named types for simple validators, and enables the
MatchAllcombinator with the same type. The trade-off is that the type cannot carry additional methods. This is idiomatic Go for single-operation contracts.
CompletionFunc (function type alias)#
- Package:
github.com/spf13/cobra - File:
completions.go:139 - Declaration:
type CompletionFunc = func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) - Purpose: Contract for dynamic tab completion providers. Stored in
Command.ValidArgsFunction(for positional arg completions) and in a per-flag registry (flagCompletionFunctionsmap, guarded bysync.RWMutex) for flag-value completions. The completion engine invokes the function at completion time, passing the partially typed string. - Implementations: Any
func(*Command, []string, string) ([]Completion, ShellCompDirective). Consumer applications supply these as named functions or closures. Cobra’s internal__completecommand invokes whichever function is registered for the current completion context. - Design quality: Declared as a Go type alias (
=), which means it is exactly the underlying function type — no additional wrapping. This maximizes interoperability: any conforming function literal can be assigned without explicit conversion. The three-argument signature is clean and carries all the context a completion provider needs.
io.Reader / io.Writer (stdlib interfaces — used as stream contracts)#
- Package:
io(stdlib) - File:
command.go:198-202,command.go:289-432 - Methods (io.Writer):
Write(p []byte) (n int, err error) - Methods (io.Reader):
Read(p []byte) (n int, err error) - Purpose: The
Commandstruct holdsinReader io.Reader,outWriter io.Writer, anderrWriter io.Writeras private fields. Public setters (SetIn,SetOut,SetErr) and getters (InOrStdin,OutOrStdout,ErrOrStderr) expose these. The getters walk the parent chain — if a command has no writer set, it falls back to the parent’s, ultimately falling back toos.Stdout/os.Stderr/os.Stdin. All shell completion script generators (GenBashCompletion,GenFishCompletion, etc.) and help/usage rendering acceptio.Writerfor output. - Design quality: Classic Go I/O testability pattern. By accepting
io.Writerrather than*os.File, consumers can injectbytes.Bufferin tests. The parent-chain fallback is particularly elegant: setting streams once on the root propagates automatically to all descendants.
Command function fields (implicit hook contracts)#
- Package:
github.com/spf13/cobra - File:
command.go:128-146 - Declarations (selected):
PersistentPreRun func(cmd *Command, args []string) PersistentPreRunE func(cmd *Command, args []string) error PreRun func(cmd *Command, args []string) PreRunE func(cmd *Command, args []string) error Run func(cmd *Command, args []string) RunE func(cmd *Command, args []string) error PostRun func(cmd *Command, args []string) PostRunE func(cmd *Command, args []string) error PersistentPostRun func(cmd *Command, args []string) PersistentPostRunE func(cmd *Command, args []string) error - Purpose: These function fields are cobra’s primary extensibility surface — the mechanism by which consumer code becomes part of the CLI. They are not a named type; each is an anonymous function type stored as a struct field. The execution engine invokes them in a fixed lifecycle order:
PersistentPreRun → PreRun → ValidateArgs → ValidateFlags → Run → PostRun → PersistentPostRun. - Design quality: The decision to use struct fields rather than a
CommanderorRunnableinterface is architecturally central. It avoids naming overhead for the most common case (a singleRunclosure) while still permitting the full lifecycle. TheEvariants (returningerror) duplicate each hook; a single optional-error signature could have been cleaner, but the dual approach preserves backward compatibility and avoids nil-check boilerplate for simple commands.
Interface patterns#
Size distribution: Cobra has exactly one named interface (
SliceValue, 1 method). All other contracts are expressed as named function types (PositionalArgs) or type aliases (CompletionFunc). TheCommandstruct’s function fields are anonymous function types. This is an unusually low interface count for a project of this influence.Embedding: No interface embedding is used anywhere in cobra.
SliceValueis a standalone single-method interface.Implicit satisfaction:
SliceValueis defined by the consumer (cobra) for detection of pflag types. It is satisfied implicitly by pflag’sStringSliceVar,IntSliceVar, etc. — those types were not written with cobra’sSliceValuein mind, but happen to satisfy it. This is the “accept interfaces, return structs” pattern taken to its logical extreme: cobra defines the smallest possible interface it needs, and existing pflag types satisfy it accidentally.stdlib interfaces used:
io.Reader—Command.inReader,SetIn,InOrStdinio.Writer—Command.outWriter/errWriter,SetOut/SetErr, all completion script generators, help/usage renderingerror— thePositionalArgsfunction type and all*Ehook fields returnerrorfmt.Stringer— not explicitly used by cobracontext.Context— not an interface in the sense of extensibility, butCommand.ctxandExecuteContextacceptcontext.Context(stdlib interface)
Key abstractions#
PositionalArgs(function type) — the most-used contract surface for consumer customization of argument validation. Its combinator (MatchAll) makes it composable. This is the pattern cobra uses instead of aValidatorinterface.CompletionFunc(type alias) — the extensibility point for dynamic completions. Every command and flag can attach aCompletionFunc; cobra’s completion engine invokes these at tab-completion time. This is effectively a single-method interface expressed as a function type alias.SliceValue(interface) — the only true named interface in cobra. Minimal by design (1 method), defined by cobra to detect pflag multi-value types without importing pflag’s own, wider interface. A clean example of interface minimalism and consumer-side definition.io.Reader/io.Writer— the I/O abstraction that makes cobra-based CLIs trivially testable. The parent-chain fallback pattern for stream resolution is architecturally elegant: a singleSetOuton the root propagates to all descendants.Command lifecycle function fields (
Run,RunE,PreRun, etc.) — not a named interface, but functionally the most important contracts in the entire project. The choice to use function fields rather than aRunnableinterface shapes how every cobra consumer writes code.
Interface-driven extensibility#
Cobra’s extensibility model is almost entirely function-field-based, not interface-based. This is a deliberate design philosophy:
- Help/usage customization: Via
SetHelpFunc(func(*Command, []string)),SetUsageFunc(func(*Command) error),SetHelpTemplate(string)— function fields and string templates, not interfaces. - Flag completion: Via
RegisterFlagCompletionFunc(flagName string, f CompletionFunc)— a function registry pattern. - Positional arg validation: Via
Args PositionalArgsfield — a named function type with a combinator. - I/O redirection: Via
io.Reader/io.Writerfields onCommand— the one place where stdlib interfaces are used. - Global callbacks: Via
OnInitialize()/OnFinalize()— appending to package-level slices.
The single true interface (SliceValue) is used only internally for type detection during completion — it is not part of cobra’s public extensibility story.
Assessment: This design maximizes ergonomic ease for the dominant use case (writing a CLI with inline closures) at the cost of formal expressibility. Consumers cannot mock a cobra.Command against an interface, cannot substitute a different execution engine, and cannot replace the flag parsing subsystem via an interface. For a framework of cobra’s scope (pure library, not a runtime), these constraints are reasonable trade-offs. The function-type-as-interface idiom is idiomatic Go and results in cleaner call sites than single-method interface satisfaction would require.