UPDATE:
Since writing these over 3 years ago I have since learned a lot more about GoLang and its type system. As such I now realize that GoLang has type aliases to address the need that I was writing an experience report about.
ORIGINAL POST:
One of the niftier aspects of programming in Go is you can create named types derived out of existing types.
For example, let’s consider storing and passing a URL. Depending on their needs programmers get to choose either a string or a class to represent URLs in many others languages. Often times programmers will choose a string to minimize the complexity of working with a URL as an object and for for the convenience of representing a URL as a literal, e.g. "https://google.com"
. But choice of a string results in loss of type safety for type-checked languages.
Types are types
Things are different with Go. In Go you can define a type that derives from other types, including basic types. And then we can use that type when declaring structure properties, parameters and variables and get full type safety.
For example, here we have a struct
with a Website
property declared as type URL
. You can assign a string literal directly to a URL, but you cannot assign a variable of type string
to a struct property declared as URL
type. Further if you typecast the string
to URL
you of course can assign it to a URL
type:
package main
type URL string
type Company struct {
Name string
Website URL
}
func NewCompany(name string) *Company {
return &Company{
Name: name,
}
}
func (me *Company) SetURL(url URL) {
// Type URL can be assigned directly to type URL
me.Website = url
}
func main() {
c := NewCompany("Google")
// Use of a string literal okay
c.Website = "http://google.com"
//...
c.Website = "Facebook"
url := "http://facebook.com"
// Assign string to URL; this will NOT compile
c.Website = url
// Assign string typecast to URL; this WILL compile
c.Website = URL(url)
}
Now you may feel this is nice but not compelling, and possibly not worth the effort of constant type-casting. And I would agree.
Differentiating similar/related types of values
But what inspired me to write this was the issues I was having keeping properties and parameters straight on a project related to differentiating between relatives URL paths and absolute URLs.
So consider this update. It adds a property that is designed to contain relative URL paths to products to our Company
objects and a method named GetProductURLs()
that returns an array of AbsoluteURL
s. This does not really illustrate how having these types can help you keep the logic in your code straight in your head — that would require a much longer blog post — but it does illustrate the mechanics of typing strings and then casting them back and forth between string
and declared types:
type AbsoluteURL string
type RelativeURLPath string
type Company struct {
Name string
Website AbsoluteURL
ProductPaths []RelativeURLPath
}
func (me *Company) GetProductURLs() []AbsoluteURL {
urls := make([]AbsoluteURL,len(me.ProductPaths))
for i,pp := range me.ProductPaths {
urls[i] = AbsoluteURL(fmt.Sprintf("%s/%s/",
strings.TrimRight(string(me.Website),"/"),
strings.TrimLeft(string(pp),"/"),
))
}
return urls
}
func main() {
c := &Company{Name: "Microsoft"}
c.Website = "https://www.microsoft.com"
c.ProductPaths = []RelativeURLPath{
"office",
"azure",
"windows",
"sharepoint",
}
for _,u := range c.GetProductURLs() {
fmt.Println(u)
}
}
This is also an experience report.
All of the above is great. Really. But it also shows what a PITA Go can be regarding the need for casting when you start typing everything. Which makes me think, does it really need to be this difficult?
One (obvious-to-me) potential solution
And after contemplation I think it does not. Go already allows you to assign a string literal to any typed value receiver — such as a variable, parameter or structure property — so why not also allow assigning those same string-derived variables, parameters and properties anywhere a generic string value can be accepted?
Given a string-derived type is constraining a string, I cannot see how assigning a more constrained string to a less constrained string could cause any type safety concerns? So, assuming the Go team were to relax this constraint on more constrained types we could drop the string()
casting and rewrite the operative part of the method GetProductURLs()
making it just a bit easier to read:
urls[i] = AbsoluteURL(fmt.Sprintf("%s/%s/",
strings.TrimRight(me.Website,"/"),
strings.TrimLeft(pp,"/"),
))
Not hugely different, but in a large codebase this type of simplification can really add up in a good way.
And obviously this concept of more vs. less constrained does not apply only to string
. It should apply to any types that are derived directly from other types in this form:
More Constrained vs. Less Constrained
Here is a simply example illustrating how the amount of constraint should allow typing to be relaxed at times:
type MoreConstrained LessContrained
type LessContrained string
var alpha MoreConstrained
alpha = "abc"
var alphanum LessConstrained
alphanum = "abc123"
alphanum = alpha // This should compile, rightly so
alpha = alphanum // This already fails to compile, rightly so
Call for consideration
Here is hoping that the architects of the Go language see this and agree enough with the concerns that they are willing to loosen the exact matching restrictions of types in Go.