Documentation
¶
Overview ¶
Package set is a small wrapper around the official reflect package that facilitates loose type conversion, assignment into native Go types, and utilities to populate deeply nested Go structs.
Data Types ¶
In this package documentation float, int, and uint include the bit-specific types. For example float includes float32 and float64, int includes int8, int16, int32, and int64, etc.
Scalar Types ¶
This package considers the following types to be scalars:
bool, float, int, uint, & string pointer to any of the above; e.g: *bool, *float, *int, etc nested pointer to any of the above; e.g: ***bool, *****string, etc
Package Name Collision ¶
I called this package `set` because I like typing short identifiers and I don't really use packages that implement logical set operations such as union or intersection.
I also like the semantics of typing: set.V().To() // i.e. set value to
If you find yourself dealing with name collision here are some alternate imports that are still short and keep the semantics (with varying success):
import ( assign "github.com/nofeaturesonlybugs/set" accept "github.com/nofeaturesonlybugs/set" coerce "github.com/nofeaturesonlybugs/set" from "github.com/nofeaturesonlybugs/set" make "github.com/nofeaturesonlybugs/set" pin "github.com/nofeaturesonlybugs/set" will "github.com/nofeaturesonlybugs/set" )
Basic Type Coercion ¶
A simple example with type coercion:
b, i := true, 42
set.V(&b).To("False") // Sets b to false
set.V(&i).To("3.14") // Sets i to 3
In general:
var t T // T is a target data type, t is a variable of that type. var s S // S is a source data, s is a variable of that type. set.V(&t).To(s) // Sets s into t with a "best effort" approach.
If t is not a pointer or is a pointer that is set to nil then pass the address of t:
var t bool // Pass the address when t is not a pointer. set.V(&t) var t *int // Pass the address when t is a pointer but set to nil. set.V(&t) var i int t := &i // Do not pass the address when t is a pointer and not set to nil. set.V(t) // Note that values assigned go into i!
Scalar to Scalar of Same Type ¶
When T and S are both scalars of the same type then assignment with this package is the same as direct assignment:
var a int // Call this T
var b int // Call this S
set.V(&a).To(b) // Effectively the same as: a = b
// pointers to structs
x := &T{} // Call this T
y := &T{"data", "more data"} // Call this S
set.V(&x).To(y) // Effectively the same as: x = y
This package grants no benefit when used as such.
Scalar to Scalar of Different Type ¶
When T and S are both scalars but with different types then assignments can be made with type coercion:
a := int(0) // Call this T
b := uint(42) // Call this S
set.V(&a).To(b) // This coerces b into a if possible.
set.V(&a).To("-57") // Also works.
set.V(&a).To("Hello") // Returns an error.
Pointers Are Tricky But Work Well ¶
If a pointer already contains a memory address then you do not need to pass the pointer's address to set:
var b bool
bp := &b // bp already contains an address
set.V(bp).To("1") // b is set to true
If a pointer does not contain an address then passing the pointer's address allows set to create the data type and assign it to the pointer:
var bp *bool
set.V(&bp).To("True") // bp is now a pointer to bool and *bp is true.
This works even if the pointer is multiple levels of indirection:
var bppp ***bool
set.V(&bppp).To("True")
fmt.Println(***bppp) // Prints true
If S is a pointer it will be dereferenced until the final value; if the final value is a scalar then it will be coerced into T:
var ippp ***int s := "42" sp := &s spp := &sp set.V(&ippp).To(spp) fmt.Println(***ippp) // Prints 42
Scalars to Slices ¶
When T is a slice and S is a scalar then T is assigned a slice with S as its single element:
var b []bool
set.V(&b).To("True") // b is []bool{ true }
Slices to Scalars ¶
When T is a scalar and S is a slice then the last element of S is assigned to T:
var b bool
set.V(&b).To([]bool{ false, false, true } ) // b is true, coercion not needed.
set.V(&b).To([]interface{}{ float32(1), uint(0) }) // b is false, coercion needed.
If S is a nil or empty slice then T is set to an appropriate zero value.
Slices to Slices ¶
When T and S are both slices set always creates a new slice []T and copies elements from []S into []T.
var t []bool
var s []interface{}
s = []interface{}{ "true", 0, float64(1) }
set.V(&t).To(s) // t is []bool{ true, false, true }
var t []bool
var s []bool
s = []bool{ true, false, true }
set.V(&t).To(s) // t is []bool{ true, false, true } and t != s
If a single element within []S can not be coerced into an element of T then []T will be empty:
var t []int
var s []string
s = []string{ "42", "24", "Hello!" }
set.V(&t).To(s) // t is []int{} because "Hello" can not coerce.
Populating Structs with Value.Fill() and a Getter ¶
Structs can be populated by using Value.Fill() and a Getter; note the function is type casted to a set.GetterFunc.
// An example getter.
myGetter := set.GetterFunc(func( name string ) interface{} {
switch name {
case "Name":
return "Bob"
case "Age":
return "42"
default:
return nil
}
})
Populating a struct by field; i.e. the struct field names are the names passed to the Getter:
type T struct {
Name string
Age uint
}
var t T
set.V(&t).Fill(myGetter)
Populating a struct by struct tag; i.e. if the struct tag exists on the field then the tag's value is passed to the Getter:
type T struct {
SomeField string `key:"Name"`
OtherField uint `key:"Age"`
}
var t T
set.V(&t).FillByTag("key", myGetter)
Populating Nested Structs with Value.Fill() and a Getter ¶
To populate nested structs a Getter needs to return a Getter for the given name:
myGetter := set.GetterFunc(func(key string) interface{} {
switch key {
case "name":
return "Bob"
case "age":
return "42"
case "address":
return set.GetterFunc(func(key string) interface{} {
switch key {
case "street1":
return "97531 Some Street"
case "street2":
return ""
case "city":
return "Big City"
case "state":
return "ST"
case "zip":
return "12345"
default:
return nil
}
})
default:
return nil
}
})
Using the Getter above:
type Address struct {
Street1 string `key:"street1"`
Street2 string `key:"street2"`
City string `key:"city"`
State string `key:"state"`
Zip string `key:"zip"`
}
type Person struct {
Name string `key:"name"`
Age uint `key:"age"`
Address Address `key:"address"`
}
var t Person
set.V(&t).FillByTag("key", myGetter)
Maps as Getters ¶
A more practical source of data for a Getter might be a map. To use a map as a Getter the map key has to be assignable to string; e.g: string or interface{}. If the map contains nested maps that also meet the criteria for becoming a Getter then those maps can be used to populate nested structs.
m := map[string]interface{}{
"name": "Bob",
"age": 42,
"address": map[interface{}]string{
"street1": "97531 Some Street",
"street2": "",
"city": "Big City",
"state": "ST",
"zip": "12345",
},
}
myGetter := set.MapGetter(m)
type Address struct {
Street1 string `key:"street1"`
Street2 string `key:"street2"`
City string `key:"city"`
State string `key:"state"`
Zip string `key:"zip"`
}
type Person struct {
Name string `key:"name"`
Age uint `key:"age"`
Address Address `key:"address"`
}
var t Person
set.V(&t).FillByTag("key", myGetter)
Populating Structs with Mapper, Mapping, and BoundMap ¶
If you need to populate or traverse structs using strings as lookups consider using a Mapper. A Mapper traverses a type T and generates a Mapping which contains members to facilitate accessing struct field indeces with strings.
When you index into Mapping.Indeces you will receive a slice of ints representing the indeces into the nested structure to the desired field.
For convenience a Mapper can create a BoundMapping which binds the Mapping to an instance of T. The BoundMapping can then be used to update the data within the instance. See the BoundMapping examples.
Rebinding ¶
Both Value and BoundMapping support Rebind(). There is an amount of overhead instantiating either Value or BoundMapping and most of that overhead occurs with type introspection via reflect. In tight-loop situations where either *Value or BoundMapping are used to alter many values of the same type this overhead can be costly and is unnecessary since the first *Value or BoundMapping already contains the type information.
For optimal performance in tight loop situations create a single instance of *Value or BoundMapping and then call Rebind() with a new instance of data you wish to manipulate.
For example change:
for _, value := range someSliceOfTypeT {
v := set.V( &value ) // <-- Expensive -- type information gathered for each instance created.
// do something with v
}
to:
v := set.Value( &T{} ) // Create v once!
for _, value := range someSliceOfTypeT {
v.Rebind( &value ) // <-- Reuses existing type information -- more performant!
// do something with v
}
Examples Subdirectory ¶
The examples subdirectory contains multiple examples for this package; separating them keeps this package documentation a little cleaner.
var myVar bool value := set.V(&myVar)
To see what you can do with `value` in the above code find the `Bool` type in the examples package.
// Assuming SomeType is a struct. var myVar SomeType value := set.V(&myVar)
To see what you can do with `value` in the above code find the `Struct` type in the examples package.
Index ¶
- Variables
- func Writable(v reflect.Value) (V reflect.Value, CanWrite bool)
- type BoundMapping
- type CanPanic
- type Field
- type Getter
- type GetterFunc
- type Mapper
- type Mapping
- type TypeInfo
- type TypeInfoCache
- type TypeList
- type Value
- func (me *Value) Append(items ...interface{}) error
- func (me *Value) Copy() *Value
- func (me *Value) FieldByIndex(index []int) (reflect.Value, error)
- func (me *Value) FieldByIndexAsValue(index []int) (*Value, error)
- func (me *Value) Fields() []Field
- func (me *Value) FieldsByTag(key string) []Field
- func (me *Value) Fill(getter Getter) error
- func (me *Value) FillByTag(key string, getter Getter) error
- func (me *Value) NewElem() (*Value, error)
- func (me *Value) Rebind(arg interface{})
- func (me *Value) To(arg interface{}) error
- func (me *Value) Zero() error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultMapper = &Mapper{
Join: "_",
}
DefaultMapper joins names by "_" but performs no other modifications.
var Panics = CanPanic{}
Panics is a global instance of CanPanic; it is provided for convenience.
var TypeCache = NewTypeInfoCache()
TypeCache is a global TypeInfoCache
Functions ¶
func Writable ¶
Writable attempts to make a reflect.Value usable for writing. It will follow and instantiate nil pointers if necessary.
Example ¶
package main
import (
"fmt"
"reflect"
"github.com/nofeaturesonlybugs/set"
)
func main() {
var value, writable reflect.Value
var ok bool
var s string
var sp *string
value = reflect.ValueOf(s)
writable, ok = set.Writable(value)
fmt.Printf("ok= %v\n", ok)
value = reflect.ValueOf(sp)
writable, ok = set.Writable(value)
fmt.Printf("ok= %v\n", ok)
value = reflect.ValueOf(&sp)
writable, ok = set.Writable(value)
writable.SetString("Hello")
fmt.Printf("ok= %v sp= %v\n", ok, *sp)
}
Output: ok= false ok= false ok= true sp= Hello
Types ¶
type BoundMapping ¶
type BoundMapping interface {
// Assignables returns a slice of interfaces by field names where each element is a pointer
// to the field in the bound variable.
//
// The second argument, if non-nil, will be the first return value. In other words pre-allocating
// rv will cut down on memory allocations. It is assumed that if rv is non-nil that len(fields) == len(rv)
// and the lengths are not checked, meaning your program could panic.
//
// During traversal this method will instantiate struct fields that are themselves structs.
//
// An example use-case would be obtaining a slice of pointers for Rows.Scan() during database
// query results.
Assignables(fields []string, rv []interface{}) ([]interface{}, error)
// Copy creates an exact copy of the BoundMapping. Since a BoundMapping is implicitly tied to a single
// value sometimes it may be useful to create and cache a BoundMapping early in a program's initialization and
// then call Copy() to work with multiple instances of bound values simultaneously.
Copy() BoundMapping
// Err returns an error that may have occurred during repeated calls to Set(); it is reset on
// calls to Rebind()
Err() error
// Field returns the *Value for field.
Field(field string) (*Value, error)
// Fields returns a slice of interfaces by field names where each element is the field value.
//
// The second argument, if non-nil, will be the first return value. In other words pre-allocating
// rv will cut down on memory allocations. It is assumed that if rv is non-nil that len(fields) == len(rv)
// and the lengths are not checked, meaning your program could panic.
//
// During traversal this method will instantiate struct fields that are themselves structs.
//
// An example use-case would be obtaining a slice of query arguments by column name during
// database queries.
Fields(fields []string, rv []interface{}) ([]interface{}, error)
// Rebind will replace the currently bound value with the new variable I. If the underlying types do
// not match a panic will occur.
Rebind(I interface{})
// Set effectively sets V[field] = value.
Set(field string, value interface{}) error
}
BoundMapping binds a Mapping to a specific variable instance V.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/nofeaturesonlybugs/set"
)
func main() {
type Person struct {
First string
Last string
}
values := []map[string]string{
{"first": "Bob", "last": "Smith"},
{"first": "Sally", "last": "Smith"},
}
mapper := &set.Mapper{
Transform: strings.ToLower,
}
var people []Person
bound := mapper.Bind(&Person{})
for _, m := range values {
person := Person{}
bound.Rebind(&person)
for fieldName, fieldValue := range m {
bound.Set(fieldName, fieldValue)
}
if err := bound.Err(); err != nil {
fmt.Println(err.Error())
}
people = append(people, person)
}
fmt.Println(people[0].First, people[0].Last)
fmt.Println(people[1].First, people[1].Last)
}
Output: Bob Smith Sally Smith
type CanPanic ¶
type CanPanic struct{}
CanPanic is a namespace for operations prioritizing speed over type safety or error checking. Reach for this namespace when your usage of the `set` package is carefully crafted to ensure panics will not result from your actions.
Methods within CanPanic will not validate that points are non-nil.
It is strongly encouraged to create suitable `go tests` within your project when reaching for CanPanic.
You do not need to create or instantiate this type; instead you can use the global `var Panics`.
type Field ¶
type Field struct {
Value *Value
Field reflect.StructField
TagValue string
}
Field is a struct field; it contains a Value and a reflect.StructField.
type Getter ¶
type Getter interface {
// Get accepts a name and returns the value.
Get(name string) interface{}
}
Getter returns a value by name.
type GetterFunc ¶
type GetterFunc func(name string) interface{}
GetterFunc casts a function into a Getter.
func (GetterFunc) Get ¶
func (me GetterFunc) Get(name string) interface{}
Get accepts a name and returns the value.
type Mapper ¶
type Mapper struct {
// If the types you wish to map contain embedded structs or interfaces you do not
// want to map to string names include those types in the Ignored member.
//
// See also NewTypeList().
Ignored TypeList
//
// Struct fields that are also structs or embedded structs will have their name
// as part of the generated name unless it is included in the Elevated member.
//
// See also NewTypeList().
Elevated TypeList
//
// Types in this list are treated as scalars when generating mappings; in other words
// their exported fields are not mapped and the mapping created targets the type as
// a whole. This is useful when you want to create mappings for types such as sql.NullString
// without traversing within the sql.NullString itself.
TreatAsScalar TypeList
//
// A list of struct tags that will be used for name generation in order of preference.
// An example would be using this feature for both JSON and DB field name specification.
// If most of your db and json names match but you occasionally want to override the json
// struct tag value with the db struct tag value you could set this member to:
// []string{ "db", "json" } // struct tag `db` used before struct tag `json`
Tags []string
//
// Join specifies the string used to join generated names as nesting increases.
Join string
//
// If set this function is called when the struct field name is being used as
// the generated name. This function can perform string alteration to force all
// names to lowercase, string replace, etc.
Transform func(string) string
// contains filtered or unexported fields
}
Mapper is used to traverse structures to create Mappings and then navigate the nested structure using string keys.
Instantiate mappers as pointers:
myMapper := &set.Mapper{}
Example ¶
package main
import (
"fmt"
"strings"
"github.com/nofeaturesonlybugs/set"
)
func main() {
type CommonDb struct {
Pk int `t:"pk"`
CreatedTime string `t:"created_time"`
UpdatedTime string `t:"updated_time"`
}
type Person struct {
CommonDb `t:"common"`
Name string `t:"name"`
Age int `t:"age"`
}
var data Person
{
mapper := &set.Mapper{
Elevated: set.NewTypeList(CommonDb{}),
Join: "_",
}
mapping := mapper.Map(&data)
fmt.Println(strings.Replace(mapping.String(), "\t\t", " ", -1))
}
{
fmt.Println("")
fmt.Println("lowercase with dot separators")
mapper := &set.Mapper{
Join: ".",
Transform: strings.ToLower,
}
mapping := mapper.Map(&data)
fmt.Println(strings.Replace(mapping.String(), "\t\t", " ", -1))
}
{
fmt.Println("")
fmt.Println("specify tags")
mapper := &set.Mapper{
Join: "_",
Tags: []string{"t"},
}
mapping := mapper.Map(&data)
fmt.Println(strings.Replace(mapping.String(), "\t\t", " ", -1))
}
}
Output: [0 0] Pk [0 1] CreatedTime [0 2] UpdatedTime [1] Name [2] Age lowercase with dot separators [0 0] commondb.pk [0 1] commondb.createdtime [0 2] commondb.updatedtime [1] name [2] age specify tags [0 0] common_pk [0 1] common_created_time [0 2] common_updated_time [1] name [2] age
Example (TreatAsScalar) ¶
package main
import (
"database/sql"
"fmt"
"time"
"github.com/nofeaturesonlybugs/set"
)
func main() {
type T struct {
S string
T time.Time
N sql.NullString
}
mapping := set.DefaultMapper.Map(T{})
if mapping.Get("T") != nil {
fmt.Println("T is mapped because time.Time is automatically treated as a scalar.")
}
if mapping.Get("N") == nil {
fmt.Println("N can not be found because sql.NullString was not treated as a scalar.")
}
if mapping.Get("N_Valid") != nil {
fmt.Println("N_Valid was mapped because the exported fields in sql.NullString were mapped.")
}
//
// Now we'll treat sql.NullString as a scalar when mapping.
mapper := &set.Mapper{
TreatAsScalar: set.NewTypeList(sql.NullString{}),
}
mapping = mapper.Map(T{})
if mapping.Get("N") != nil {
fmt.Println("N is now mapped to the entire sql.NullString member.")
v, _ := set.V(&T{}).FieldByIndex(mapping.Get("N"))
fmt.Printf("N's type is %v\n", v.Type())
}
}
Output: T is mapped because time.Time is automatically treated as a scalar. N can not be found because sql.NullString was not treated as a scalar. N_Valid was mapped because the exported fields in sql.NullString were mapped. N is now mapped to the entire sql.NullString member. N's type is sql.NullString
func (*Mapper) Bind ¶
func (me *Mapper) Bind(I interface{}) BoundMapping
Bind creates a Mapping bound to a specific instance I of a variable.
Example ¶
package main
import (
"fmt"
"github.com/nofeaturesonlybugs/set"
)
func main() {
type CommonDb struct {
Pk int
CreatedTime string
UpdatedTime string
}
type Person struct {
CommonDb
Name string
Age int
}
Print := func(p Person) {
fmt.Printf("Person: pk=%v created=%v updated=%v name=%v age=%v\n", p.Pk, p.CreatedTime, p.UpdatedTime, p.Name, p.Age)
}
data := []Person{{}, {}}
Print(data[0])
Print(data[1])
//
mapper := &set.Mapper{
Elevated: set.NewTypeList(CommonDb{}),
}
bound := mapper.Bind(&data[0])
bound.Set("Pk", 10)
bound.Set("CreatedTime", "-5h")
bound.Set("UpdatedTime", "-2h")
bound.Set("Name", "Bob")
bound.Set("Age", 30)
if err := bound.Err(); err != nil {
fmt.Println(err.Error())
}
//
bound.Rebind(&data[1])
bound.Set("Pk", 20)
bound.Set("CreatedTime", "-15h")
bound.Set("UpdatedTime", "-12h")
bound.Set("Name", "Sally")
bound.Set("Age", 20)
if err := bound.Err(); err != nil {
fmt.Println(err.Error())
}
//
Print(data[0])
Print(data[1])
}
Output: Person: pk=0 created= updated= name= age=0 Person: pk=0 created= updated= name= age=0 Person: pk=10 created=-5h updated=-2h name=Bob age=30 Person: pk=20 created=-15h updated=-12h name=Sally age=20
func (*Mapper) Map ¶
Map adds T to the Mapper's list of known and recognized types.
Map is written to be goroutine safe in that multiple goroutines can call it. If multiple goroutines call Map simultaneously it is not guaranteed that each goroutine receives the same Mapping instance; however it is guaranteed that each instance will behave identically.
If you depend on Map returning the same instance of Mapping for a type T every time it is called then you should call it once for each possible type T synchronously before using the Mapper in your goroutines.
Mappings that are returned are shared resources and should not be altered in any way. If this is your use-case then create a copy of the Mapping with Mapping.Copy.
type Mapping ¶
type Mapping struct {
// Keys is a slice of key names in the order they were encountered during the mapping.
Keys []string
// Using a mapped name as a key the []int is the slice of indeces needed to traverse
// the mapped struct to the nested field.
Indeces map[string][]int
// Using a mapped name as a key the StructField is the nested field. Access to this
// member is useful if your client package needs to inspect struct-fields-by-mapped-name; such
// as in obtaining struct tags.
StructFields map[string]reflect.StructField
}
Mapping is the result of traversing nested structures to generate a mapping of Key-to-Fields.
func (*Mapping) Get ¶
Get returns the indeces associated with key in the mapping. If no such key is found a nil slice is returned.
type TypeInfo ¶
type TypeInfo struct {
// True if the Value is a scalar type:
// bool, float32, float64, string
// int, int8, int16, int32, int64
// uint, uint8, uint16, uint32, uint64
IsScalar bool
// True if the Value is a map.
IsMap bool
// True if the Value is a slice.
IsSlice bool
// True if the Value is a struct.
IsStruct bool
// Kind is the reflect.Kind; when Stat() or StatType() were called with a pointer this will be the final
// kind at the end of the pointer chain. Otherwise it will be the original kind.
Kind reflect.Kind
// Type is the reflect.Type; when Stat() or StatType() were called with a pointer this will be the final
// type at the end of the pointer chain. Otherwise it will be the original type.
Type reflect.Type
// When IsMap or IsSlice are true then ElemType will be the reflect.Type for elements that can be directly
// inserted into the map or slice; it is not the type at the end of the chain if the element type is a pointer.
ElemType reflect.Type
// When IsStruct is true then StructFields will contain the reflect.StructField values for the struct.
StructFields []reflect.StructField
}
TypeInfo summarizes information about a type T in a meaningful way for this package.
type TypeInfoCache ¶
type TypeInfoCache interface {
// Stat accepts an arbitrary variable and returns the associated TypeInfo structure.
Stat(T interface{}) TypeInfo
// StatType is the same as Stat() except it expects a reflect.Type.
StatType(T reflect.Type) TypeInfo
}
TypeInfoCache builds a cache of TypeInfo types; when requesting TypeInfo for a type T that is a pointer the TypeInfo returned will describe the type T' at the end of the pointer chain.
If Stat() or StatType() are called with nil or an Interface(nil) then a zero TypeInfo is returned; essentially nothing useful can be done with the type needed to be described.
func NewTypeInfoCache ¶
func NewTypeInfoCache() TypeInfoCache
NewTypeInfoCache creates a new TypeInfoCache.
type TypeList ¶
TypeList is a list of reflect.Type.
func NewTypeList ¶
func NewTypeList(args ...interface{}) TypeList
NewTypeList creates a new TypeList type from a set of instantiated types.
type Value ¶
type Value struct {
// TypeInfo describes the type T in WriteValue. When the value is created with a pointer P
// this TypeInfo will describe the final type at the end of the pointer chain.
//
// To conserve memory and maintain speed this TypeInfo object may be shared with
// other *Value instances. Altering the members within TypeInfo will most likely
// crash your program with a panic.
//
// Treat this value as read only.
TypeInfo
// CanWrite specifies if WriteValue.CanSet() would return true.
CanWrite bool
// TopValue is the original value passed to V() but wrapped in a reflect.Value.
TopValue reflect.Value
// WriteValue is a reflect.Value representing the modifiable value wrapped within this *Value.
//
// If you call V( &t ) then CanWrite will be true and WriteValue will be a usable reflect.Value.
// If you call V( t ) where t is not a pointer or does not point to allocated memory then
// CanWrite will be false and any attempt to set values on WriteValue will probably panic.
//
// All methods on this type that alter the value Append(), Fill*(), To(), etc work on this
// value. Generally you should avoid it but it's also present if you really know what you're doing.
WriteValue reflect.Value
// When IsMap or IsSlice are true then ElemTypeInfo is a TypeInfo struct describing the element types.
ElemTypeInfo TypeInfo
// contains filtered or unexported fields
}
Value wraps around a Go variable and performs magic.
func V ¶
func V(arg interface{}) *Value
V returns a new Value.
Memory is possibly created when calling this function:
// No memory is created because b is a local variable and we pass its address. var b bool v := set.V(&b) // No memory is created because bp points at an existing local variable and we pass the pointer bp. var b bool bp := &b v := set.V(bp) // Memory is created because the local variable is an unallocated pointer AND we pass its address. var *bp bool v := set.V(&bp) // bp now contains allocated memory.
func (*Value) Append ¶
Append appends the item(s) to the end of the Value assuming it is some type of slice and every item can be type-coerced into the slice's data type. Either all items are appended without an error or no items are appended and an error is returned describing the type of the item that could not be appended.
func (*Value) Copy ¶ added in v0.3.0
Copy creates a clone of the *Value and its internal members.
If you need to create many *Value for a type T in order to Rebind(T) in a goroutine architecture then consider creating and caching a V(T) early in your application and then calling Copy() on that cached copy before using Rebind().
func (*Value) FieldByIndex ¶
FieldByIndex returns the nested field corresponding to index.
Key differences between this method and the built-in method on reflect.Value.FieldByIndex() are the built-in causes panics while this one will return errors and this method will instantiate nil struct members as it traverses.
func (*Value) FieldByIndexAsValue ¶
FieldByIndexAsValue calls into FieldByIndex and if there is no error the resulting reflect.Value is wrapped within a call to V() to return a *Value.
func (*Value) Fields ¶
Fields returns a slice of Field structs when Value is wrapped around a struct; for all other values nil is returned.
This function has some overhead because it creates a new *Value for each struct field. If you only need the reflect.StructField information consider using the public StructFields member.
func (*Value) FieldsByTag ¶
FieldsByTag is the same as Fields() except only Fields with the given struct-tag are returned and the TagValue member of Field will be set to the tag's value.
func (*Value) Fill ¶
Fill iterates a struct's fields and calls Set() on each one by passing the field name to the Getter. Fill stops and returns on the first error encountered.
func (*Value) FillByTag ¶
FillByTag is the same as Fill() except the argument passed to Getter is the value of the struct-tag.
func (*Value) NewElem ¶
NewElem instantiates and returns a *Value that can be Panics.Append()'ed to this type; only valid if Value.ElemType describes a valid type.
func (*Value) Rebind ¶
func (me *Value) Rebind(arg interface{})
Rebind will swap the underlying original value used to create *Value with the incoming value if:
Type(Original) == Type(Incoming).
If Rebind succeeds the following public members will have been replaced appropriately:
CanWrite TopValue WriteValue
Reach for this function to translate:
var slice []T
// populate slice
for _, item := range slice {
v := set.V( item ) // Creates new *Value every iteration -- can be expensive!
// manipulate v in order to affect item
}
to:
var slice []T
v := set.V( T{} ) // Create a single *Value for the type T
// populate slice
for _, item := range slice {
v.Rebind( item ) // Reuse the existing *Value -- will be faster!
// manipulate v in order to affect item
}
func (*Value) To ¶
To attempts to assign the argument into Value.
If *Value is wrapped around an unwritable reflect.Value or the type is reflect.Invalid an error will be returned. You probably forgot to call set.V() with an address to your type.
If the assignment can not be made but the wrapped value is writable then the wrapped value will be set to an appropriate zero type to overwrite any existing data.
set.V(&T).To(S)
T is scalar, S is scalar, same type
-> direct assignment
T is pointer, S is pointer, same type and level of indirection
-> direct assignment
If S is a pointer then dereference until final S value and continue...
T is scalar, S is scalar, different types
-> assignment with attempted type coercion
T is scalar, S is slice []S
-> T is assigned S[ len( S ) - 1 ]; i.e. last element in S if length greater than 0.
T is slice []T, S is scalar
-> T is set to []T{ S }; i.e. a slice of T with S as the only element.
T is slice []T, S is slice []S
-> T is set to []T{ S... }; i.e. a new slice with elements from S copied.
-> Note: T != S; they are now different slices; changes to T do not affect S and vice versa.
-> Note: If the elements themselves are pointers then, for example, T[0] and S[0] point
at the same memory and will see changes to whatever is pointed at.
Example (Addressability) ¶
package main
import (
"fmt"
"github.com/nofeaturesonlybugs/set"
)
func main() {
fmt.Println("When using Value.To the target Value must be addressable.")
//
{
var value bool
if err := set.V(value).To(true); err != nil {
// Expected
fmt.Println("1. Error because address-of value was not passed.")
} else {
fmt.Printf("1. Value is %v\n", value)
}
}
{
var value bool
if err := set.V(&value).To(true); err != nil {
fmt.Println("2. Error because address-of value was not passed.")
} else {
// Expected
fmt.Printf("2. Value is %v\n", value)
}
}
{
var value *int
if err := set.V(value).To(42); err != nil {
// Expected
fmt.Println("3. Even though value is a pointer itself its address still needs to be passed.")
if value == nil {
// Expected
fmt.Println("3. Also worth noting the pointer remains nil.")
}
} else {
fmt.Printf("3. Value is %v\n", value)
}
}
{
var value *int
if err := set.V(&value).To(42); err != nil {
fmt.Println("4. Even though value is a pointer itself its address still needs to be passed.")
if value == nil {
fmt.Println("4. Also worth noting the pointer remains nil.")
}
} else {
// Expected
fmt.Printf("4. Value is %v\n", *value)
if value != nil {
// Expected
fmt.Println("4. A pointer-to-int was created!")
}
}
}
{
var value ***int
if err := set.V(&value).To(42); err != nil {
fmt.Println("5. Even though value is a pointer itself its address still needs to be passed.")
if value == nil {
fmt.Println("5. Also worth noting the pointer remains nil.")
}
} else {
// Expected
fmt.Printf("5. Value is %v\n", ***value)
fmt.Println("5. Multiple pointers had to be created for this to work!")
}
}
{
value := new(int)
if err := set.V(value).To(42); err != nil {
fmt.Println("6. Even though value is a pointer itself its address still needs to be passed.")
} else {
// Expected
fmt.Printf("6. Value is %v\n", *value)
}
}
{
value := new(**int)
if err := set.V(value).To(42); err != nil {
fmt.Println("7. Even though value is a pointer itself its address still needs to be passed.")
} else {
// Expected
fmt.Printf("7. Value is %v\n", ***value)
}
}
{
var value string
if err := set.V(&value).To("8. It works with strings too."); err == nil {
fmt.Println(value)
}
}
{
var value ***string
if err := set.V(&value).To("9. String pointers are no different."); err == nil {
fmt.Println(***value)
}
}
}
Output: When using Value.To the target Value must be addressable. 1. Error because address-of value was not passed. 2. Value is true 3. Even though value is a pointer itself its address still needs to be passed. 3. Also worth noting the pointer remains nil. 4. Value is 42 4. A pointer-to-int was created! 5. Value is 42 5. Multiple pointers had to be created for this to work! 6. Value is 42 7. Value is 42 8. It works with strings too. 9. String pointers are no different.