Skip to content

Latest commit

 

History

History
368 lines (280 loc) · 16.6 KB

types.md

File metadata and controls

368 lines (280 loc) · 16.6 KB

cty types

This page covers in detail all of the primitive types and compound type kinds supported within cty. For more general background information, see the cty overview.

Common Operations and Integration Methods

The following methods apply to all values:

  • Type returns the type of the value, as a cty.Type instance.
  • Equals returns a cty.Bool that is cty.True if the receiver and the given other value are equal. This is an operation method, so its result will be unknown if either argument is unknown.
  • RawEquals is similar to Equals except that it doesn't implement the usual special behavior for unknowns and dynamic values, and it returns a native Go bool as its result. This method is intended for use in tests; Equals should be preferred for most uses.
  • IsKnown returns true if the receiver is a known value, or false if it is an unknown value.
  • IsNull returns true if the receiver is a null value, or false otherwise.

All values except capsule-typed values can be serialized with the builtin Go package encoding/gob. Values can also be used with the %#v pattern in the fmt package to print out a Go-oriented serialization of the value.

Primitive Types

cty.Number

The number type represents what we'll clumsily call "JSON numbers". Technically, this means the set of numbers that have a canonical decimal representation in our JSON encoding and that can be represented in memory with 512 bits of binary floating point precision.

Since these numbers have high precision, there is little need to worry about integer overflow/underflow or over-zealous rounding during arithmetic operations. In particular, cty.Number can represent the full range of int64 with no loss. However, numbers are still finite in memory and subject to approximation in binary-to-decimal and decimal-to-binary conversions, and so can't accurately represent all real numbers.

Eventually a calling application will probably want to convert a number to one of the Go numeric types, at which point its range will be constrained to fit within that type, generating an error if it does not fit. Because the number range is larger than all of the Go integer types, it's always possible to convert a whole number to a Go integer without any loss, as long as it value is within the required range.

The following additional operations are supported on numbers:

  • Absolute converts a negative value to a positive value of the same magnitude.
  • Add computes the sum of two numbers.
  • Divide divides the receiver by another number.
  • GreaterThan returns cty.True if the receiver is greater than the other given number.
  • GreaterThanOrEqualTo returns cty.True if the receiver is greater than or equal to the other given number.
  • LessThan returns cty.True if the receiver is less than the other given number.
  • LessThanOrEqualTo returns cty.True if the receiver is less than or equal to the other given number.
  • Modulo computes the remainder from integer division of the receiver by the other given number.
  • Multiply computes the product of two numbers.
  • Negate inverts the sign of the number.
  • Subtract computes the difference between two numbers.

cty.Number values can be constructed using several different factory functions:

  • ParseNumberVal creates a number value by parsing a decimal representation of it given as a string. This is the constructor that most properly represents the full documented range of number values; the others below care convenient for many cases, but have a more limited range.
  • NumberIntVal creates a number value from a native int64 value.
  • NumberUIntVal creates a number value from a native uint64 value.
  • NumberFloatVal creates a number value from a native float64 value.
  • NumberVal creates a number value from a *big.Float, from the math/big package. This can preserve arbitrary big floats without modification, but comes at the risk of introducing precision inconsisistencies. Prefer the other constructors for most uses.

The core API only allows extracting the value from a known number as a *big.Float using the AsBigFloat method. However, the gocty package provides a more convenient way to convert numbers to any native Go number type, with automatic range checking to ensure that the value fits into the target type.

The following numbers are provided as package variables for convenience:

  • cty.Zero is the number zero.
  • cty.PositiveInfinity represents positive infinity as a number. All other numbers are less than this value.
  • cty.NegativeInfinity represents negative infinity as a number. All other numbers are greater than this value.

Note that the two infinity values are always out of range for a conversion to any Go primitive integer type.

cty.String

The string type represents a sequence of unicode codepoints.

There are no additional operations supported for strings.

cty.String values can be constructed using the cty.StringVal factory function. The native Go string passed in must be a valid UTF-8 sequence, and it will be normalized such that any combining diacritics are converted to precomposed forms where available. (Technically-speaking, the mapping applied is the NFC normalization as defined in the relevant Unicode specifications.)

The AsString method can be called on a string value to obtain the native Go string representation of a known string, after normalization.

cty.Bool

The bool type represents boolean (true or false) values.

The following additional operations are supported on bool values:

  • And computes the logical AND operation for two boolean values.
  • Not returns the boolean opposite of the receiver.
  • Or computes the logical OR operation for two boolean values.

Calling applications may either work directly with the predefined cty.True and cty.False variables, or dynamically create a boolean value using cty.BoolVal.

The True method returns a native Go bool representing a known boolean value. The False method returns the opposite of it.

Collection Type Kinds

cty has three different kinds of collection type. All three of them are parameterized with a single element type to produce a collection type. The difference between the kinds is how the elements are internally organized and what operations are used to retrieve them.

cty.List types

List types are ordered sequences of values, accessed using consecutive integers starting at zero.

The following operations apply to values of a list type:

  • Index can be passed an integer number less than the list's length to retrieve one of the list elements.
  • HasIndex can be used to determine if a particular call to Index would succeed.
  • Length returns a number representing the number of elements in the list. The highest integer that can be passed to Index is one less than this number.

List types are created by passing an element type to the function cty.List. List values can be created by passing a type-homogenous []cty.Value to cty.ListVal, or by passing an element type to cty.ListValEmpty.

The following integration methods can be used with known list-typed values:

  • LengthInt returns the length of the list as a native Go int.
  • ElementIterator returns an object that can be used to iterate over the list elements.
  • ForEachElement runs a given callback function for each element.

cty.Map types

Map types are collection values that are each assigned a unique string key.

The following operations apply to values of a map type:

  • Index can be passed a string value to retrieve the corresponding element.
  • HasIndex can be used to determine if a particular call to Index would succeed.
  • Length returns a number representing the number of elements in the map.

Map types are created by passing an element type to the function cty.Map. Map values can be created by passing a type-homogenous map[string]cty.Value to cty.MapVal, or by passing an element type to cty.MapValEmpty.

The following integration methods can be used with known map-typed values:

  • LengthInt returns the number of elements as a native Go int.
  • ElementIterator returns an object that can be used to iterate over the map elements in lexicographical order by key.
  • ForEachElement runs a given callback function for each element in the same order as the ElementIterator.

cty.Set types

Set types are collection values that model a mathematical set, where every possible value is either in or out of the set. Thus each set element value is its own identity in the set, and a given value cannot appear twice in the same set.

The following operations apply to values of a set type:

  • HasIndex can be used to determine whether a particular value is in the receiving set..
  • Length returns a number representing the number of elements in the set.
  • Index is not particularly useful for sets, but for symmetry with the other collection types it may be passed a value that is in the set and it will then return that same value.

Set types are created by passing an element type to the function cty.Set. Set values can be created by passing a type-homogenous []cty.Value to cty.SetVal, though the result is undefined if two values in the slice are equal. Alternatively, an empty set can be constructed using cty.SetValEmpty.

The following integration methods can be used with known set-typed values:

  • LengthInt returns the number of elements as a native Go int.
  • ElementIterator returns an object that can be used to iterate over the set elements in an undefined (but consistent) order.
  • ForEachElement runs a given callback function for each element in the same order as the ElementIterator.

Set membership is determined by equality, which has an interesting consequence for unknown values. Since unknown values are never equal to one another, theoretically an infinite number of unknown values can be in a set (constrained by available memory) but can never be detected by calls to HasIndex.

A set with at least one unknown value in it has an unknown length, because the unknown values may or may not match each other (and thus coalesce into a single value) once they become known. However, if a set contains a mixture of known and unknown values then HasIndex will return true for those values because they are guaranteed to remain present no matter what final known value each of the unknown values takes on.

Structural Types

cty has two different kinds of structural type. They have in common that they combine a number of values of arbitrary types together into a single value, but differ in how those values are internally organized and in which operations are used to retrieve them.

cty.Object types

Object types each have zero or more named attributes that each in turn have their own type.

The following operation applies to values of an object type:

  • GetAttr returns the value of an attribute given its name.

The set of valid attributes for an object type can be inspected using the following methods on the type itself:

  • AttributeTypes returns a map[string]Type describing the types of all of the attributes.
  • AttributeType returns the type of a single attribute given its name.
  • HasAttribute returns true if the type has an attribute with the given name.

Object types are constructed by passing a map[string]Type to cty.Object. Object values can be created by passing a map[string]Value to cty.ObjectVal, in which the keys and value types define the object type that is implicitly created for that value.

The variable cty.EmptyObject contains the object type with no attributes, and cty.EmptyObjectVal is the only non-null, known value of that type.

There is currently-experimental support for creating object types where one or more attributes is annotated as being "optional", using the alternative constructor cty.ObjectWithOptionalAttrs. The behavior of that function or of any other function working with its result is subject to change even in future minor versions of cty. The optional-attribute annotations are considered only during type conversion, so for more information refer to the guide Converting between cty types.

cty.Tuple types

Tuple types each have zero or more elements, each with its own type, arranged in a sequence and accessed by integer numbers starting at zero.

A tuple type is therefore somewhat similar to a list type, but rather than representing an arbitrary number of values of a single type it represents a fixed number of values that may have different types.

The following operations apply to values of a tuple type:

  • Index can be passed an integer number less than the tuple's length to retrieve one of the tuple elements.
  • HasIndex can be used to determine if a particular call to Index would succeed.
  • Length returns a number representing the number of elements in the tuple. The highest integer that can be passed to Index is one less than this number.

Tuple types are created by passing a []cty.Type to the function cty.Tuple. Tuple values can be created by passing a []cty.Value to cty.TupleVal, in which the value types define the tuple type that is implicitly created for that value.

The variable cty.EmptyTuple contains the tuple type with no elements, and cty.EmptyTupleVal is the only non-null, known value of that type.

The following integration methods can be used with known tuple-typed values:

  • LengthInt returns the length of the tuple as a native Go int.
  • ElementIterator returns an object that can be used to iterate over the tuple elements.
  • ForEachElement runs a given callback function for each element.

The Dynamic Pseudo-Type

The dynamic pseudo-type is not a real type but is rather a placeholder for a type that isn't known.

One consequence of this being a "pseudo-type" is that there is no known, non-null value of this type, but cty.DynamicVal is the unknown value of this type, and a null value without a known type can be represented by cty.NullVal(cty.DynamicPseudoType).

This pseudo-type serves two similar purposes as a placeholder type:

  • When cty.DynamicVal is used in an operation with another value, the result is either itself cty.DynamicVal or it is an unknown value of some suitable type. This allows the dynamic pseudo-type to be used as a placeholder during type checking, optimistically assuming that the eventually-determined type will be compatible and failing at that later point if not.
  • cty.DynamicPseudoType can be used with the type TestConformance method to declare that any type is permitted in the type being tested for conformance.

cty doesn't have sum types (i.e. union types), so cty.DynamicPseudoType can be used also to represent situations where two or more specific types are allowed, under the assumption that more specific type checking will be done within the calling application's own logic even though it cannot be expressed directly within the cty type system.

Capsule Types

Capsule types are a special kind of type that allows a calling application to "smuggle" otherwise-unsupported Go values through the cty type system.

Such types and their associated values have no defined meaning in cty. The interpreter for a language building on cty might use capsule types for passing language-specific objects between functions provided in that language.

A capsule type is created using the function cty.Capsule, which takes a "friendly name" for the type along with a reflect.Type that defines what type of Go value will be encapsulated in values of this type. A capsule-typed value can then be created by passing the capsule type and a pointer to a native value of the encapsulated type to cty.CapsuleVal.

The integration method EncapsulatedValue allows the encapsulated data to then later be retrieved.

Capsule types compare by reference, so each call to cty.Capsule produces a distinct type. Capsule values compare by pointer equality, so two capsule values are equal if they have the same capsule type and they encapsulate a pointer to the same object.

Due to the strange nature of capsule types, they are not totally supported by all of the other packages that build on the core cty API. They should be used with care and the documentation for other packages should be consulted for information about caveats and constraints relating to their use.

It's possible for a calling application to write additional logic to make capsule types support a subset of operations that are generally expected to work for values of any type. For more information, see capsule type operation definitions.