github.com/haraldrudell/parl@v0.4.176/sets/e-string.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package sets 7 8 import ( 9 "fmt" 10 "reflect" 11 12 "github.com/haraldrudell/parl/internal/cyclebreaker" 13 ) 14 15 // eString is an improved %v string converter. 16 // E is a concrete type, that may be indirect, or interface 17 // - E can be: 18 // - — a type without String method 19 // - — a type with pointer-receiver String method 20 // - — a type with value-receiver String method 21 // - — a pointer to those 22 // - issues: 23 // - — if E is pointer to type without String method, 24 // a uintptr pointer is output ‘0x1400000e4b0’ where 25 // it is desirable to print values not pointers 26 // - — if E has pointer-receiver String but is not pointer, 27 // type assertion fails to find the String method 28 // - — 29 // - if e is basic type, %v: eString(1) → ‘1’ 30 // - if e is *basic type, %v: eString(&1) → ‘1’ 31 // - if e is *basic type, %v: eString(&abc) → ‘abc’ 32 // - if e is String pointer receiver: eString((*) String()) → ‘string’ 33 // - if e is *String pointer receiver: eString(&(*) String()) → ‘string’ 34 // - if e is String value receiver: eString(() String()) → ‘string’ 35 // - if e is *String value receiver: eString(&() String()) → ‘string’ 36 func eString[E any](e E) (s string) { 37 38 // this type assertion works if: 39 // - e is any(struct) where struct has value-receiver String method 40 // - e is any(&struct) where struct has value-receiver String method 41 // - e is any(&struct) where struct has pointer-receiver String method 42 // this type assertion fails if: 43 // - e is nil 44 // - e does not have String method 45 // - e has more than one indirection 46 // - e is non-pointer struct with pointer-receiver String method 47 if stringer, ok := any(e).(fmt.Stringer); ok { 48 s = stringer.String() 49 return 50 } 51 52 // handle case where: 53 // - e is non-pointer struct with pointer-receiver String method 54 // - because e is concrete type, we can use address operator 55 // - and then convert that to interface and do type assertion 56 if stringer, ok := any(&e).(fmt.Stringer); ok { 57 s = stringer.String() 58 return 59 } 60 61 // handle non-pointer types and nil 62 var isNil = cyclebreaker.IsNil(e) 63 var typeName string 64 var isPointer bool 65 if !isNil { 66 typeName = fmt.Sprintf("%T", e) 67 isPointer = typeName[0] == '*' 68 } 69 if isNil || !isPointer { 70 // some basic or composite type value or 71 // - typed or untyped nil pointer: ‘<nil>’ 72 s = fmt.Sprintf("%v", e) 73 return 74 } 75 76 // E is non-nil pointer to something 77 // - it is not allowed to indirect E and 78 // - it is not desirable to print using default %v: uintptr like ‘0x1400000e4c8’ 79 // - if the value is extracted using unsafe.Pointer, 80 // %v will print whatever value the unsafe.Pointer is claimed to be 81 // - since E is a real type and not any, reflection will work 82 // - reflection can extract the pointed-to value and assign it to 83 // an interface variable 84 85 // e is not nil and known to be pointer 86 // - the concrete value stored in e 87 var reflectValue = reflect.ValueOf(e) 88 // extract the value E is pointing to 89 var pointedToValue = reflectValue.Elem() 90 // store the pointed-to value as interface value 91 var anyValue = pointedToValue.Interface() 92 // print anyValue’s dynamic type using %v 93 s = fmt.Sprintf("%v", anyValue) 94 95 return 96 }