Skip to main content
  1. Articles/

Golang Release 1.21: cmp

·789 words·4 mins· loading · loading ·
Marko Milojevic
Author
Marko Milojevic
Software engineer and architect. Golang and LLM enthusiast. Awful chess player, gym rat, harmonica newbie and cat lover.
New features in Golang - This article is part of a series.
Part 2: This Article

As the new release of Go came this summer, many of us started to look for the improvements inside its ecosystem. Many new features were introduced, including updates to the tool command to support backward and forward compatibility. New packages appeared inside the Standard Library, including maps and slices. In this article we are covering improvements introduced with the new cmp package.

The new package offers three new functions. All of them rely on Generics, a feature introduced in Go version 1.18, which has opened up possibilities for many new features. The cmp package introduces new functions for comparing values of Ordered constraint.

Let’s dive into each of them.

Ordered constraint and Compare function
#

The constraint Ordered encompasses all types that support comparison operators for values, specifically, <, <=, >= and >. This includes all numeric types in Go, as well as strings.

Ordered Constraint

type Ordered interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
		~float32 | ~float64 |
		~string
}

Once we understand what the Ordered constraint includes, we can focus on the first function from the cmp package, which is the Compare function. Below, you can find its signature:

Compare Function

// Compare returns
//
//	-1 if x is less than y,
//	 0 if x equals y,
//	+1 if x is greater than y.
// ...
func Compare[T Ordered](x, y T) int

The signature, along with the function description, makes it much easier to understand. The Compare function expects two arguments of the same type, compares their values, and returns a result that represents the comparison status:

  • -1 if the first argument is less than the second.
  • 0 if the arguments’ values are equal.
  • 1 if the first argument is greater than the second.

Let’s prove such claim:

Compare numerals

fmt.Println(cmp.Compare(1, 2))
// Output:
// -1
fmt.Println(cmp.Compare(1, 1))
// Output:
// 0
fmt.Println(cmp.Compare(2, 1))
// Output:
// 1

Compare strings

fmt.Println(cmp.Compare("abc", "def"))
// Output:
// -1
fmt.Println(cmp.Compare("qwe", "qwe"))
// Output:
// 0
fmt.Println(cmp.Compare("abcde", "abcc"))
// Output:
// 1

Above, we can see practical examples of the Compare function for both numerals and strings. Indeed, the return values can only belong to the set of numbers {-1, 0, 1}, as defined in the description.

Function Less
#

In addition to the function Compare, we got another, similar function Less. Although it’s rather easy to understand what is used for, let’s check its signature:

Function Less

func Less[T Ordered](x, y T) bool

Again, this method expects two arguments of the same type that must adhere to the Ordered constraint. It returns the boolean value true if the first argument is less than the second one.

Less function with numerals

fmt.Println(cmp.Compare(1, 2))
// Output:
// -1
fmt.Println(cmp.Compare(1, 1))
// Output:
// 0
fmt.Println(cmp.Compare(2, 1))
// Output:
// 1

Less function with strings

fmt.Println(cmp.Less("abc", "def"))
// Output:
// true
fmt.Println(cmp.Less("qwe", "qwe"))
// Output:
// false
fmt.Println(cmp.Less("abcde", "abcc"))
// Output:
// false

Bonus: functions min and max
#

In addition to the functions mentioned in the cmp package, the new Go release introduced two new built-in functions: min and max. They are also based on Generics and can be used without importing any package, just like other built-in functions. Below, you can find their signatures:

Min function

func min[T cmp.Ordered](x T, y ...T) T

Max function

func max[T cmp.Ordered](x T, y ...T) T

Both min and max functions are variadic functions, and they expect at least one argument. As you can see, only the x argument of type T is required, and the y argument is a trailing argument that can accept many or no values of the same type T. The result of these functions is a single value of the same type T, representing the minimum or maximum of all the values. Let’s check some examples:

Examples with numerals

fmt.Println(min(1, 2, 3))
// Output:
// 1
fmt.Println(max(1, 2, 3))
// Output:
// 3

fmt.Println(min(1))
// Output:
// 1
fmt.Println(max(1))
// Output:
// 1

Examples with strings

fmt.Println(min("abc", "def"))
// Output:
// abc
fmt.Println(max("abc", "def"))
// Output:
// def

fmt.Println(min("qwe", "qwe", "qwe"))
// Output:
// qwe
fmt.Println(max("qwe"))
// Output:
// qwe

In all the examples above, we can see how the new functions behave in various situations. This includes their normal behavior with only one argument, as well as when more than two arguments are provided.

Conclusion
#

New version of Golang, 1.21, delivered many new updates, affecting standard library as well. In this article we checked how functions from cmp packages work. Those new methods give us now possibility to easily compare any ordered types in Go.

Useful Resources
#

New features in Golang - This article is part of a series.
Part 2: This Article

Related

Golang Release 1.21: maps

·1573 words·8 mins· loading · loading
Not too long ago, we witnessed a new release of our favorite programming language. The Go team didn’t disappoint us once again. They introduced numerous new features, including updates to the tool command to support backward and forward compatibility. As always, the standard library has received new updates, and the first one we’ll explore in this article is the new maps package. The new package offers only five new functions (two additional ones were removed from the package: Values and Keys), but they provide significant value. All of them rely on Generics, a feature introduced in Go version 1.18, which has opened up possibilities for many new features. The map package clearly provides new tools for Go maps. In this particular case, it introduces new functions for checking map equality, deleting items from maps, copying items into maps, and cloning maps. Let’s dive into each of them. Equal and EqualFunc # First, let’s examine the pair of functions used to check map equality: Equal and EqualFunc. The first one is a straightforward function that checks the equality of two provided maps as function arguments. The second one allows you to pass an additional argument that defines how you plan to examine the equality of values inside the maps. Here are their signatures: Function Equal func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool Function EqualFunc func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool The Equal function is easier to understand. It simply defines two generic types, M1 and M2, which represent maps of two other generic types, K and V. Obviously, K is for map keys, and it allows any comparable value. The second type is V, representing map values, and it also allows being of a comparable type. The EqualFunc function is slightly more complicated. First, it doesn’t assume that the values in the maps are of the same type, nor do they have to be comparable. For that reason, it introduces an additional argument, which is an equality function for the values in the maps. This way, we can compare two maps that have the same keys but not the same values, and we can define the logic for comparing if they are equal. Simple usage of Equal function first := map[string]string{ "key1": "value1", } second := map[string]string{ "key1": "value1", } fmt.Println(maps.Equal(first, second)) // Output: // true third := map[string]string{ "key1": "value1", } fourth := map[string]string{ "key1": "wrong", } fmt.Println(maps.Equal(third, fourth)) // Output: // false In the example above, there are no surprises. We use four maps to test the Equal function. In the first case, two maps are equal, but in the second case, their values are not the same. The following example is also easy.

Golang Tutorial: Contract Testing with PACT

·2678 words·13 mins· loading · loading
My favorite part of software development is writing tests, whether they are unit tests or integration tests. I enjoy the process immensely. There’s a certain satisfaction in creating a test case that uncovers a function’s failure. It brings me joy to discover a bug during development, knowing that I’ve fixed it before anyone encounters it in a test environment or, worse, in production. Sometimes, I stay up late just to write more tests; it’s like a hobby. I even spent around 30 minutes on my wedding day writing unit tests for my personal project, but don’t tell my wife! The only thing that used to bother me was dealing with integration issues between multiple Microservices. How could I ensure that two Microservices, each with specific versions, wouldn’t face integration problems? How could I be certain that a new version of a Microservice didn’t break its API interface, rendering it unusable for others? This information was crucial to have before launching extensive scenarios in our end-to-end testing pipeline. Otherwise, we’d end up waiting for an hour just to receive feedback that we’d broken the JSON schema. Then, one day in the office, I heard a rumor that we were planning to use Contract Testing. I quickly checked the first article I found, and I was amazed. It was a breakthrough. Contract Testing # There are many excellent articles about Contract testing, but the one I like the most is from Pactflow. Contract testing ensures that two parties can communicate effectively by testing them in isolation to verify if both sides support the messages they exchange. One party, known as the Consumer, captures the communication with the other party, referred to as the Provider, and creates the Contract. This Contract serves as a specification for the expected requests from the Consumer and the responses from the Provider. Application code automatically generates Contracts, typically during the unit testing phase. Automatic creation ensures that each Contract accurately reflects the latest state of affairs. Contract testing After the Consumer publishes the Contract, the Provider can use it. In its code, likely within unit tests, the Provider conducts Contract verification and publishes the results. In both phases of Contract testing, we work solely on one side, without any actual interaction with the other party. Essentially, we are ensuring that both parties can communicate with each other within their separate pipelines. As a result, the entire process is asynchronous and independent. If either of these two phases fails, both the Consumer and Provider must collaborate to resolve integration issues. In some cases, the Consumer may need to adapt its integrational code, while in others, the Provider may need to adjust its API.

Golang Tutorial: Generics with Gorm

·1741 words·9 mins· loading · loading
After months and years of talking, trying things out, and testing, we’ve finally reached a big moment in our favorite programming language. The new Golang version, 1.18, is here. We knew it would bring significant changes to Go’s codebase, even before Generics was officially released. For a long time, when we wanted to make our code more general and abstract, we used code generators in Go. Learning what the “Go way” of doing things was challenging for many of us, but it also led to many breakthroughs. It was worth the effort. Now, there are new possibilities on the horizon. Many new packages have emerged, giving us ideas on how we can improve the Go ecosystem with reusable code that makes life easier for all of us. This inspiration led me to create a small proof of concept using the Gorm library. Now, let’s take a look at it. Source code # When I wrote this article, it relied on a GitHub Repository. The code served as a Go library proof of concept, with my intention to continue working on it. However, it was not yet suitable for production use, and I had no plans to offer production support at that time. You can find the current features by following the link, and below, there is a smaller sample snippet. Example Usage package main import ( "github.com/ompluscator/gorm-generics" // some imports ) // Product is a domain entity type Product struct { // some fields } // ProductGorm is DTO used to map Product entity to database type ProductGorm struct { // some fields } // ToEntity respects the gorm_generics.GormModel interface func (g ProductGorm) ToEntity() Product { return Product{ // some fields } } // FromEntity respects the gorm_generics.GormModel interface func (g ProductGorm) FromEntity(product Product) interface{} { return ProductGorm{ // some fields } } func main() { db, err := gorm.Open(/* DB connection string */) // handle error err = db.AutoMigrate(ProductGorm{}) // handle error // initialize a new Repository with by providing // GORM model and Entity as type repository := gorm_generics.NewRepository[ProductGorm, Product](db) ctx := context.Background() // create new Entity product := Product{ // some fields } // send new Entity to Repository for storing err = repository.Insert(ctx, &product) // handle error fmt.Println(product) // Out: // {1 product1 100 true} single, err := repository.FindByID(ctx, product.ID) // handle error fmt.Println(single) // Out: // {1 product1 100 true} } Why have I picked ORM for PoC? # Coming from a background in software development with traditional object-oriented programming languages like Java, C#, and PHP, one of the first things I did was search Google for a suitable ORM for Golang. Please forgive my inexperience at the time, but that’s what I was expecting. It’s not that I can’t work without an ORM. It’s just that I don’t particularly like how raw MySQL queries appear in the code. All that string concatenation looks messy to me. On the other hand, I always prefer to dive right into writing business logic, with minimal time spent thinking about the underlying data storage. Sometimes, during the implementation, I change my mind and switch to different types of storage. That’s where ORMs come in handy.