cmhzc
cmhzc

Reputation: 60

Go type definition operation "inheritance"?

The Go language specification describes type definition as follows:

A type definition creates a new, distinct type with the same underlying type and operations as the given type, and binds an identifier to it. The new type is called a defined type. It is different from any other type, including the type it is created from.

I have two questions about this description:

  1. What does "operation as the given type" mean, and what is the scope of "operation"(i.e. what counts as operation)? Say I define type A int[] and type B map[string]int, does "same operation" means I can use indexing on variables of type A and key-related operations on variables of type B?

  2. I don't quite understand this description, why is the new type different with its underlying type while keeping the operations? So, the only difference is that they have different methods?

Upvotes: 2

Views: 1147

Answers (2)

icza
icza

Reputation: 417482

Look at the meaning of "operation as the given type" with context:

"A type definition creates a new, distinct type with the same operation as the given type."

And yes, this means if you could use the index operator on the original type, you can also index the new type. If you could apply the + addition operator on the original type, you can also apply it on the new type. If you could apply the <- receive operator on the original type (e.g. a bidirectional channel), you can also apply on the new type etc.

Basically everything you (may) do with a value is an operation. Applying operators on it, passing it to a function, calling a method on it. Whether an operation is allowed / valid is determined by the value's type, and whether a method call is valid depends on if the method set of the value's type contains the given method.

The new type is different because the type definition creates a new, named type, and Spec: Type identity:

Two types are either identical or different.

A defined type is always different from any other type.

The new type is different by definition. The new type will have zero methods "inherited" from the original type, which comes handy when you don't want the new type implementing certain interfaces, for details and examples, see Inheritance syntax. What is the difference?

You may of course add new methods to your new type. It may be unwanted to add methods to the existing type, or it may be impossible (e.g. because the old type may be a builtin type or may be defined in a package not under your control, and methods can only be added in the defining package).

Type identity (being different) also plays a role in assignability. E.g. a value of unnamed type can be assigned to a variable of named type if the underlying types match, but a value of named type cannot be assigned to a variable of another named type even if the underlying types match (without explicit conversion).

For example:

type Foo []int
type Bar Foo

var i []int = []int{1}
var f Foo = i // OK, []int is unnamed type
var b Bar = f // Not OK, Foo is a named type

Note that the "operations allowed" will have greater significance in the upcoming Go versions, as generics is added to the next (1.18) release, where you use constraints for type parameters, and what types may be used as type arguments for those type parameters is restricted by the operations that can be applied on certain types. For example if we have a simple generic add() function:

func add[T constraints.Ordered | constraints.Complex](a, b T) T {
    return a + b
}

We may call it with int, float64, complex64 for example. But if we have our own defined type:

type MyInt int

Since the same operations can be applied on values of MyInt than that of int, we can also use MyInt as a type argument for the above T type parameter:

fmt.Println(add(1, 2))       // add[int]
fmt.Println(add(1+2i, 3+4i)) // add[complex64]
// Yes, below works too, it will be string concatenation
fmt.Println(add("1", "2"))           // add[string]
fmt.Println(add(MyInt(1), MyInt(2))) // add[MyInt]

Output will be (try it on the Go Playground):

3
(4+6i)
12
3

Upvotes: 2

Vorsprung
Vorsprung

Reputation: 34307

The new type keeps the operations from the underlying type. But as it is a different, new type you can then add more operations to it. For example, I've added an "addall" method for type A

package main

import "fmt"

type A []int
type B map[string]int

func (a A) alladd(increment int) A {
    for i := 0; i < len(a); i++ {
        a[i] = a[i] + increment
    }
    fmt.Println(a[0])
    return a
}

func main() {
    var x A
    x = append(x, 122)
    x[0] = x[0] + 1
    var y B = make(B)
    y["ABC"] = 999
    x = x.alladd(27)
    fmt.Println(x, y)
}

https://go.dev/play/p/Od1_-SXk_uO

Upvotes: 0

Related Questions