github.com/ipld/go-ipld-prime@v0.21.0/datamodel/pathSegment.go (about) 1 package datamodel 2 3 import ( 4 "strconv" 5 ) 6 7 // PathSegment can describe either a key in a map, or an index in a list. 8 // 9 // Create a PathSegment via either ParsePathSegment, PathSegmentOfString, 10 // or PathSegmentOfInt; or, via one of the constructors of Path, 11 // which will implicitly create PathSegment internally. 12 // Using PathSegment's natural zero value directly is discouraged 13 // (it will act like ParsePathSegment("0"), which likely not what you'd expect). 14 // 15 // Path segments are "stringly typed" -- they may be interpreted as either strings or ints depending on context. 16 // A path segment of "123" will be used as a string when traversing a node of map kind; 17 // and it will be converted to an integer when traversing a node of list kind. 18 // (If a path segment string cannot be parsed to an int when traversing a node of list kind, then traversal will error.) 19 // It is not possible to ask which kind (string or integer) a PathSegment is, because that is not defined -- this is *only* intepreted contextually. 20 // 21 // Internally, PathSegment will store either a string or an integer, 22 // depending on how it was constructed, 23 // and will automatically convert to the other on request. 24 // (This means if two pieces of code communicate using PathSegment, 25 // one producing ints and the other expecting ints, 26 // then they will work together efficiently.) 27 // PathSegment in a Path produced by ParsePath generally have all strings internally, 28 // because there is no distinction possible when parsing a Path string 29 // (and attempting to pre-parse all strings into ints "just in case" would waste time in almost all cases). 30 // 31 // Be cautious of attempting to use PathSegment as a map key! 32 // Due to the implementation detail of internal storage, it's possible for 33 // PathSegment values which are "equal" per PathSegment.Equal's definition 34 // to still be unequal in the eyes of golang's native maps. 35 // You should probably use the string values of the PathSegment as map keys. 36 // (This has the additional bonus of hitting a special fastpath that the golang 37 // built-in maps have specifically for plain string keys.) 38 type PathSegment struct { 39 /* 40 A quick implementation note about the Go compiler and "union" semantics: 41 42 There are roughly two ways to do "union" semantics in Go. 43 44 The first is to make a struct with each of the values. 45 46 The second is to make an interface and use an unexported method to keep it closed. 47 48 The second tactic provides somewhat nicer semantics to the programmer. 49 (Namely, it's clearly impossible to have two inhabitants, which is... the point.) 50 The downside is... putting things in interfaces generally incurs an allocation 51 (grep your assembly output for "runtime.conv*"). 52 53 The first tactic looks kludgier, and would seem to waste memory 54 (the struct reserves space for each possible value, even though the semantic is that only one may be non-zero). 55 However, in most cases, more *bytes* are cheaper than more *allocs* -- 56 garbage collection costs are domininated by alloc count, not alloc size. 57 58 Because PathSegment is something we expect to put in fairly "hot" paths, 59 we're using the first tactic. 60 61 (We also currently get away with having no extra discriminator bit 62 because we use a signed int for indexes, and negative values aren't valid there, 63 and thus we can use it as a sentinel value. 64 (Fun note: Empty strings were originally used for this sentinel, 65 but it turns out empty strings are valid PathSegment themselves, so!)) 66 */ 67 68 s string 69 i int64 70 } 71 72 // ParsePathSegment parses a string into a PathSegment, 73 // handling any escaping if present. 74 // (Note: there is currently no escaping specified for PathSegments, 75 // so this is currently functionally equivalent to PathSegmentOfString.) 76 func ParsePathSegment(s string) PathSegment { 77 return PathSegment{s: s, i: -1} 78 } 79 80 // PathSegmentOfString boxes a string into a PathSegment. 81 // It does not attempt to parse any escaping; use ParsePathSegment for that. 82 func PathSegmentOfString(s string) PathSegment { 83 return PathSegment{s: s, i: -1} 84 } 85 86 // PathSegmentOfString boxes an int into a PathSegment. 87 func PathSegmentOfInt(i int64) PathSegment { 88 return PathSegment{i: i} 89 } 90 91 // containsString is unexported because we use it to see what our *storage* form is, 92 // but this is considered an implementation detail that's non-semantic. 93 // If it returns false, it implicitly means "containsInt", as these are the only options. 94 func (ps PathSegment) containsString() bool { 95 return ps.i < 0 96 } 97 98 // String returns the PathSegment as a string. 99 func (ps PathSegment) String() string { 100 switch ps.containsString() { 101 case true: 102 return ps.s 103 case false: 104 return strconv.FormatInt(ps.i, 10) 105 } 106 panic("unreachable") 107 } 108 109 // Index returns the PathSegment as an integer, 110 // or returns an error if the segment is a string that can't be parsed as an int. 111 func (ps PathSegment) Index() (int64, error) { 112 switch ps.containsString() { 113 case true: 114 return strconv.ParseInt(ps.s, 10, 64) 115 case false: 116 return ps.i, nil 117 } 118 panic("unreachable") 119 } 120 121 // Equals checks if two PathSegment values are equal. 122 // 123 // Because PathSegment is "stringly typed", this comparison does not 124 // regard if one of the segments is stored as a string and one is stored as an int; 125 // if string values of two segments are equal, they are "equal" overall. 126 // In other words, `PathSegmentOfInt(2).Equals(PathSegmentOfString("2")) == true`! 127 // (You should still typically prefer this method over converting two segments 128 // to string and comparing those, because even though that may be functionally 129 // correct, this method will be faster if they're both ints internally.) 130 func (x PathSegment) Equals(o PathSegment) bool { 131 if !x.containsString() && !o.containsString() { 132 return x.i == o.i 133 } 134 return x.String() == o.String() 135 }