Table of Contents
In today's article, I will introduce to you what is Go embedding and how it can be used in Go. Golang is not an OOP programming language. However, some of the benefits of object orientation can be applied to go structs (similar to Class), interface, and method are all available to be embedded. The embedding type will be used to use the "inherit" property in Go.
Embedded Type is declaring a type in another type but not declaring a name, a field without declaring a name is also known as an embedded field.
Embedding structs in Golang
Here is an example of embedding a struct in another one:
type Author struct {
AuthorName string
AuthorAge int
}
type Book struct {
Title string
Author // Embedded field
}
In the above example, if we name the Author
field as usual, we will have a nested struct, and if we use an Embedded field, we can consider the Book
struct to have all the fields of the Author
struct (duplicate field names are not allowed). The Book struct above will equivalent to:
type Book struct {
Title string
Content string
AuthorName string
AuthorAge int
}
We will be able to use both Book and Author struct without having to re-declare duplicate fields.
When retrieving data can also be obtained directly without going through a nested truct, for example, get the author's name instead of book.Author.AuthorName, then we just need to book.AuthorName. See the full example here:
package main
import "fmt"
type Author struct {
AuthorName string
AuthorAge int
}
type Book struct {
Title string
Author // Embedded field
}
func main() {
book1 := Book{
Title: "Book1",
Author: Author{
AuthorName: "author1",
AuthorAge: 19,
},
}
fmt.Println(book1)
fmt.Println(book1.AuthorAge)
fmt.Println(book1.Author.AuthorAge)
}
Output:
{Book1 {author1 19}}
19
19
Embedding methods in Golang
It also works well to embed structs with methods. Suppose we have access to this method for Author struct:
func (author Author) SayHello() string {
return fmt.Sprintf("I am %v. Hello GoLinuxCloud members!", author.AuthorName)
}
Now that Author struct has this method, we can call it on instances of Book as if it did too:
package main
import "fmt"
type Author struct {
AuthorName string
AuthorAge int
}
type Book struct {
Title string
Author // Embedded field
}
func main() {
book1 := Book{
Title: "Book1",
Author: Author{
AuthorName: "author1",
AuthorAge: 19,
},
}
author1 := Author{
AuthorName: "author2",
AuthorAge: 22,
}
fmt.Println(author1.SayHello())
fmt.Println(book1.SayHello())
}
func (author Author) SayHello() string {
return fmt.Sprintf("I am %v. Hello GoLinuxCloud members!", author.AuthorName)
}
Output:
I am author2. Hello GoLinuxCloud members!
I am author1. Hello GoLinuxCloud members!
When book1.SayHello() is call, the actual logic is the below method will be called. Calling SayHell() on the Book struct has a similar result to our original one, which embeds.
func (bk Book) SayHello() string {
return bk.Author.SayHello()
}
This example also highlights a key detail in the behavior of methods on embedded fields: regardless of the embedding struct it is called through, Author's SayHello() always receives a Author (the leftmost (...) in the method description). In other languages that support inheritance, such as Python and C++, inherited methods receive a reference to the subclass via which they are called. This is a significant distinction between embedding in Go and traditional inheritance.
Embedding interfaces in Golang
The interface is a group of method signatures that can be used to construct variables in the Go programming language. As far as we are aware, the Go language does not enable inheritance, but the Go interface does. An interface can incorporate other interfaces or the method signatures of other interfaces in it when embedding. You can embed any number of interfaces in a single interface. And when an interface, embed other interfaces in it if we made any changes in the methods of the interfaces.
type interface1 interface {
Method1()
}
type interface2 interface {
Method2()
}
type embedded_interface interface {
interface1
interface2
}
OR
type interface1 interface {
Method1()
}
type interface2 interface {
Method2()
}
type embedded_interface interface {
Method1()
Method2()
}
Here is an example of embedding interface into another interface:
package main
import "fmt"
// Interface 1
type inteface1 interface {
AthorDetails()
}
// Interface 2
type inteface2 interface {
CustomDetails()
}
type embedded interface {
inteface1
inteface2
}
// struct
type Author struct {
AuthorName string
AuthorAge int
}
// Implementing method of the interface 1
func (a Author) AthorDetails() {
fmt.Printf("I am %v, i am %v years old. Hello GoLinuxCloud members!\n", a.AuthorName, a.AuthorAge)
}
// Implementing method of the interface 2
func (a Author) CustomDetails() {
fmt.Printf("This is a customized message! I am %v, i am %v years old.\n", a.AuthorName, a.AuthorAge)
}
func main() {
author := Author{
AuthorName: "author1",
AuthorAge: 22,
}
// Accessing the methods of the interface 1 and 2
var f embedded = author
f.AthorDetails()
f.CustomDetails()
}
Output:
I am author1, i am 22 years old. Hello GoLinuxCloud members!
This is a customized message! I am author1, i am 22 years old.
Embedding Files in Golang
This feature was added in Go 1.16 that allows you to embed static assets into Go binaries. The allowed data types for keeping an embedded file are string
, []byte
, and embed.FS
. This means that a Go binary may contain a file that you do not have to manually download when you execute the Go binary! The presented utility embeds two different files that it can retrieve based on the given command-line argument.
package main
import (
_ "embed"
"fmt"
"os"
)
//go:embed static/firefox.png
var f1 []byte
//go:embed static/file1.txt
var f2 string
func writeToFile(s []byte, path string) error {
fd, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer fd.Close()
n, err := fd.Write(s)
if err != nil {
return err
}
fmt.Printf("wrote %d bytes\n", n)
return nil
}
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Print select 1|2")
return
}
fmt.Println("f1:", len(f1), "f2:", len(f2))
switch arguments[1] {
case "1":
filename := "/tmp/out.png"
err := writeToFile(f1, filename)
if err != nil {
fmt.Println(err)
return
}
case "2":
fmt.Print(f2)
default:
fmt.Println("Not a valid option!")
}
}
Explanation:
You need the embed package in order to embed any files in your Go binaries. As the embed
package is not directly used, you need to put _
in front of it so that the Go compiler won't complain.
You need to begin a line with //go:embed
, which denotes a Go comment but is treated in a special way, followed by the path to the file you want to embed. In this case we embed static/file1.txt, which is a binary file. The next line should define the variable that is going to hold the data of the embedded file, which in this case is a byte slice named f
1 using a byte slice is recommended for binary files because we are going to directly use that byte slice to save that binary file.
//go:embed static/firefox.png
var f1 []byte
//go:embed static/file1.txt
var f2 string
In this case we save the contents of a plain text file, which is static/textfile
, in a string variable named f2
. The writeToFile()
function is used for storing a byte slice into a file and is a helper function that can be used in other cases as well.
The switch block is responsible for returning the desired file to the user in the case of static/textfile
, the file contents are printed on the screen. For the binary file, we decided to store it as /tmp/out.png
.
Next we execute the binary:
$ go run main.go 2 f1: 32441 f2: 6 hello $ go run main.go 1 f1: 32441 f2: 6 wrote 32441 bytes $ ll /tmp/out.png -rw-r--r-- 1 root root 32441 Nov 20 14:39 /tmp/out.png $ md5sum /tmp/out.png static/firefox.png db0cad7c310dcd7673d976699293f08d /tmp/out.png db0cad7c310dcd7673d976699293f08d static/firefox.png
Using the embedding functionality, we can create a utility that embeds its own source code and prints it on screen when it gets executed! This is a fun way of using embedded files. The source code of main.go
is the following:
package main
import (
_ "embed"
"fmt"
)
//go:embed main.go
var src string
func main() {
fmt.Print(src)
}
Output
$ go run main.go
package main
import (
_ "embed"
"fmt"
)
//go:embed main.go
var src string
func main() {
fmt.Print(src)
As before, the file that is being embedded is defined in the //go:embed
line. Running main.go
prints the aforementioned code on screen.
Summary
Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface. With the explained examples below, hope you can have a better understanding of embedding in Golang.
References
https://go.dev/doc/effective_go#embedding