File: convert.md

package info (click to toggle)
golang-github-zclconf-go-cty 1.12.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,464 kB
  • sloc: makefile: 2
file content (165 lines) | stat: -rw-r--r-- 7,844 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# Converting between `cty` types

[The `convert` package](https://godoc.org/github.com/zclconf/go-cty/cty/convert)
provides a standard set of type conversion routines for moving between
types in the `cty` type system.

_Conversion_ in this context means taking a given value and producing a
new value of a different type that, in some sense, contains the same
information. For example, the number `5` can be converted to a string as
`"5"`.

Specific conversion operations are represented by type `Conversion`, which
is a function type that takes a single value as input and returns a value
or an error.

## "Safe" and "Unsafe" conversions

The `convert` package broadly organizes its supported conversions into two
types.

"Safe" conversions are ones where all values of the source type can
be represented in the target type, and thus the conversion is guaranteed to
succeed for any value of the source type.

"Unsafe" conversions, on the other hand, are able to convert only a subset
of values of the source type. Values outside of that subset will cause the
conversion function to return an error.

Converting from number to string is safe because an unambiguous string
representation can be created for any number. The converse is _unsafe_,
because while a string like `"2.5"` can be converted to a number, a string
like `"bananas"` cannot.

The calling application must choose whether to attempt unsafe conversions,
depending on whether it is willing to tolerate conversions returning errors
even though they ostensibly passed type checking. Operations that have both
safe and unsafe modes come in couplets, with the unsafe version's name
having the suffix `Unsafe`.

## Getting a Conversion

To find out if a conversion is available between two types, an application can
call either `GetConversion` or `GetConversionUnsafe`. These functions return
a valid `Conversion` if one is available, or `nil` if not.

Note that there are no conversions from a type to itself. Callers should check
if two types are equal before attempting to obtain a conversion between them.

As usual, `cty.DynamicPseudoType` serves as a special-case placeholder. It is
used in two ways, depending on whether it appears in the source or the
destination type:

* When a source type is dynamic, a special unsafe conversion is available that
  takes any value and passes it through verbatim if it matches the destination
  type, or returns an error if it does not. This can be used as part of handling
  dynamic values during a type-checking procedure, with the generated
  conversion serving as a run-time type check.

* When a _destination_ type is dynamic, a simple passthrough conversion is
  generated that does not transform the source value at all. This is supported
  so that a destination type can behave similarly to a type description used
  for a conformance check, thus allowing this package to be used to attempt
  to _make_ a type conformant, rather than merely check whether it already
  is.

## Converting a Value

A value can be converted by passing it as the argument to any conversion whose
source type matches the value's type. If the conversion is an unsafe one, the
conversion function may return an error, in which case the returned value is
invalid and must not be used.

As a convenience, the `Convert` function takes a value and a target type and
returns a converted value if a conversion is available. This is equivalent
to testing for an unsafe conversion for the value's type and then immediately
calling any discovered conversion. An error is returned if a conversion is not
available.

## Type Unification

A related idea to type _conversion_ is type _unification_. While conversion
is concerned with going from a specific source type to a specific target type,
unification is instead concerned with finding a single type that several other
types can be converted to, without any specific preference as to what the
final type is.

A good example of this would be to take a set of values provided to initialize
a list and choose a single type that all of those values can be
converted to, which then decides the element type of the final list.

The `Unify` and `UnifyUnsafe` functions are used for type unification. They
both take as input a slice of types and then return, if possible, a single
target type along with a slice of conversions corresponding to each
of the input types.

Since many type pairs support type conversions in both directions, the unify
functions must apply a preference for which direction to follow given such a
pair of types. These functions prefer safe conversions over unsafe ones
(assuming that `UnifyUnsafe` was called), and prefer lossless conversions
over potentially-lossy ones.

Type unification is a potentially-expensive operation, depending on the
complexity of the passed types and whether they are mutually conformant.

## Conversion Charts

The foundation of the available conversions is the matrix of conversions
between the primitive types. String is the most general type, since the
other two primitive types have safe conversions to string. The full
matrix for primitive types is as follows:

|         | string | number | boolean |
|---------|:------:|:------:|:-------:|
| string  |   n/a  | unsafe |  unsafe |
| number  |  safe  |   n/a  |   none  |
| boolean |  safe  |  none  |   n/a   |

The conversions for compound types are then derived from the above foundation.
For example, a list of numbers can convert to a list of strings
because a number can convert to a string.

The compound type kinds themselves have some available conversions, though:

|        |  tuple | object | list |   map  |     set    |
|--------|:------:|:------:|:----:|:------:|:----------:|
| tuple  |   n/a  |  none  | safe |  none  | safe+lossy |
| object |  none  |   n/a  | none |  safe  |    none    |
| list   | unsafe |  none  |  n/a |  none  | safe+lossy |
| map    |  none  | unsafe | none |   n/a  |    none    |
| set    | unsafe |  none  | safe |  none  |     n/a    |

Conversions between compound kinds, as shown above, are possible only
if their respective elements/attributes also have conversions available.

The conversions from structural types to collection types rely on
type unification to identify a single element type for the final collection,
and so conversion is possible only if unification is possible.

## Conversion between Object Types

There are some special considerations for conversion between distinct object
types that do not apply to conversion between types of other kinds.

For object types, `convert` implements _structural typing_ behaviors, where
the target type is considered to be a description of a set of attributes the
final result should have. There are two important additional concerns that
result from this design intent:

* If the input type has additional attributes that are not mentioned at all
  in the target type, those additional attributes are silently discarded
  during conversion, leading to a new object value that has a subset of the
  attributes of the input value, and whose type therefore conforms to the
  target type constraint.

* If any of the attributes of the target type are marked as optional using
  the **currently-experimental** `cty.ObjectWithOptionalAttrs` constructor,
  type conversion will tolerate those attributes being absent in the given
  type, and the resulting value will include appropriately-typed null value
  placeholders as the values of those omitted attributes.

    This behavior is subject to change even in future minor versions of the
    `cty` module, so that we can try it out with experimental versions of
    calling applications and adjust the details of the behavior if needed.
    Hopefully this mechanism will be stabilized in a future release, if those
    downstream experiments are successful.