Interface
In Go programming language, an interface is a collection of method signatures with a unique name. You cannot create instances of interfaces, but you can assign values of types that implement the methods defined in the interface. The default value for an interface is nil
or nil interface
because you cannot assign any other type's nil
to it.
General Syntax
type [INTERFACE NAME] interface {
[METHOD NAME] ([ARGUMENTS...]) ([RETURN TYPES...])
// ...
}
One of the most popular interfaces in Go is the error
interface, which is used for organizing, passing, and handling errors.
Defining an Interface
An interface definition essentially describes what behavior it expects from a type. In the example below, the Shape
interface expects an Area
function with no parameters and float64
return type.
type Shape interface {
Area() float64
}
Any type can be assigned to the Shape
interface as long as it correctly implements the Area
method.
In the following complete example, you can see the Database
interface and the implementing PSQL
type. The Database
interface expects three methods, which the PSQL
type satisfies.
The usage of these types is demonstrated in the main
function. It shows that you can create various database APIs that implements the Database
interface, allowing the caller to interact with them in a uniform way. This decouples the PSQL
type from the calling code. Moreover, during testing, you can "mock" the interface, making it suitable for unit testing.
package main
import (
"fmt"
)
type Database interface {
Connect() error
Query(query string) ([]byte, error)
Close() error
}
type PSQL struct {
// implementation details
}
func (m *PSQL) Connect() error {
fmt.Println("Connecting to PSQL database...")
return nil
}
func (m *PSQL) Query(query string) ([]byte, error) {
fmt.Printf("Executing query \"%s\" on PSQL database...\n", query)
return []byte("result"), nil
}
func (m *PSQL) Close() error {
fmt.Println("Closing PSQL database connection...")
return nil
}
func main() {
db := &PSQL{}
err := db.Connect()
if err != nil {
fmt.Println("Error connecting to database:", err)
return
}
defer db.Close()
result, err := db.Query("SELECT * FROM users")
if err != nil {
fmt.Println("Error executing query:", err)
return
}
fmt.Println("Result:", string(result))
}
You can run the above example here (opens in a new tab).
Anonymous Interface
An anonymous interface is an inline interface declaration that specifies a set of methods that a type must implement. Unlike named interfaces, which are reusable and declared explicitly, anonymous interfaces are declared inline and are typically used for one-off situations.
Here’s an example of a function accepting an anonymous interface:
package main
import "fmt"
func printValue(v interface {
String() string
}) {
fmt.Println(v.String())
}
type MyType struct {
Value string
}
func (m MyType) String() string {
return m.Value
}
func main() {
data := MyType{Value: "Hello, Go!"}
printValue(data) // "Hello, Go!"
}
Use Cases
-
Single-Use Contracts:
- When you need to enforce a specific behavior without defining a reusable interface.
-
Testing and Mocks:
- Anonymous interfaces can be used to quickly define expectations for mock objects in test scenarios.
-
Dynamic Function Parameters:
- Functions can accept anonymous interfaces for highly specific behavior without the codebase with named interfaces.
Anonymous Interfaces vs. Named Interfaces
Feature | Anonymous Interface | Named Interface |
---|---|---|
Reusability | Cannot be reused elsewhere. | Can be reused across code. |
Readability | Compact but can be less readable. | Explicit and self-documenting. |
Usage Scope | Best for single-use scenarios. | Ideal for shared contracts. |
Empty Interface
The empty interface, denoted as interface{}
, is a special interface that doesn't specify any method signatures. It allows you to assign values of any type to it. In a sense, you can think of every type as implicitly implementing the empty interface. There is a built-in alias for empty interface, called any
.
You can use the empty interface to work around Go's strong typing when necessary.
func printValue(obj any) {
fmt.Println(obj)
}
func main() {
printValue(42)
printValue("Hello, World!")
printValue([]int{1, 2, 3})
}
In the above example, the printValue
function takes an empty interface as a parameter, allowing you to pass values of different types to it.
Type assertion
Type assertion in Go is a mechanism used to extract the underlying value of an interface variable. It's commonly used when you have a variable of interface type and you need to retrieve its dynamic value.
Comma-ok idiom
When you use value, ok := obj.(Type)
, if the assertion is true, ok
becomes true and value gets the underlying value. If the assertion is false, ok
becomes false and value gets the zero value of the asserted type.
This is a safe way to perform type assertion.
package main
import "fmt"
type User struct {
Name string
}
func (u User) GetName() string {
return u.Name
}
func main() {
var obj interface{} = User{Name: "Alex"}
if u, ok := obj.(interface{ GetName() string }); ok {
fmt.Println(u.GetName())
} else {
fmt.Println("given object doesn't implement GetName() method")
}
}
Unsafe type assertion
package main
import "fmt"
func main() {
var obj interface{}
value := obj.(string)
fmt.Println("Value", value)
}
The code directly asserts obj
as a string
without checking if it's actually of that type. Since obj
is an empty interface and hasn't been assigned any value, this assertion is incorrect.
You can run the above example here (opens in a new tab)
Type Assertion with switch
While basic type assertions allow you to check a single type, using a switch
statement with type assertion is a more powerful and cleaner way to handle multiple possible types for an interface.
A type switch
allows you to handle multiple types that an interface might hold. It evaluates the dynamic type of an interface and executes the corresponding case
block.
Syntax:
switch v := i.(type) {
case string:
// Handle string type
case int:
// Handle int type
default:
// Handle unknown types
}
Here’s an example that demonstrates a type switch
:
package main
import "fmt"
func printType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Println("It's a string:", v)
case int:
fmt.Println("It's an int:", v)
case bool:
fmt.Println("It's a boolean:", v)
default:
fmt.Println("Unknown type")
}
}
func main() {
printType("Hello, Go!")
printType(42)
printType(true)
printType(3.14) // Unknown type
}
Run this example in your browser (opens in a new tab)