Skip to content

Commit f45ce68

Browse files
authored
[safecast] Adds safe numeric cast helpers, including generic value and reference casting, with clamping (#824)
helpers to perform casting in a safe way
1 parent bf696d6 commit f45ce68

7 files changed

Lines changed: 1535 additions & 140 deletions

File tree

changes/20260317181439.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[safecast]` Adds safe numeric cast helpers, including generic value and reference casting, with clamping

utils/collection/range.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import (
77
"github.com/ARM-software/golang-utils/utils/safecast"
88
)
99

10-
func sign[T safecast.IConvertable](x T) int64 {
10+
func sign[T safecast.IConvertible](x T) int64 {
1111
if x < 0 {
1212
return -1
1313
}
1414
return 1
1515
}
1616

17-
func determineRangeLength[T safecast.IConvertable](start, stop, step T) (length int) {
17+
func determineRangeLength[T safecast.IConvertible](start, stop, step T) (length int) {
1818
if (step > 0 && start < stop) || (step < 0 && start > stop) {
1919
length = safecast.ToInt((safecast.ToInt64(stop-start+step) - sign(step)) / safecast.ToInt64(step))
2020
}
@@ -25,7 +25,7 @@ func determineRangeLength[T safecast.IConvertable](start, stop, step T) (length
2525
// https://docs.python.org/2/library/functions.html#range
2626
//
2727
// Note: The stop value is always exclusive.
28-
func Range[T safecast.IConvertable](start, stop T, step *T) (result []T) {
28+
func Range[T safecast.IConvertible](start, stop T, step *T) (result []T) {
2929
it, length := rangeSequence(start, stop, step)
3030
result = make([]T, length)
3131
i := 0
@@ -37,12 +37,12 @@ func Range[T safecast.IConvertable](start, stop T, step *T) (result []T) {
3737
}
3838

3939
// RangeSequence returns an iterator over a range
40-
func RangeSequence[T safecast.IConvertable](start, stop T, step *T) iter.Seq[T] {
40+
func RangeSequence[T safecast.IConvertible](start, stop T, step *T) iter.Seq[T] {
4141
it, _ := rangeSequence(start, stop, step)
4242
return it
4343
}
4444

45-
func rangeSequence[T safecast.IConvertable](start, stop T, step *T) (it iter.Seq[T], length int) {
45+
func rangeSequence[T safecast.IConvertible](start, stop T, step *T) (it iter.Seq[T], length int) {
4646
s := field.Optional[T](step, 1)
4747
if s == 0 {
4848
it = func(yield func(T) bool) {}

utils/collection/range_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/ARM-software/golang-utils/utils/safecast"
1313
)
1414

15-
func testRange[T safecast.IConvertable](t *testing.T, start, stop T, step *T, expected []T) {
15+
func testRange[T safecast.IConvertible](t *testing.T, start, stop T, step *T, expected []T) {
1616
t.Helper()
1717
assert.Equal(t, expected, Range[T](start, stop, step))
1818
if reflection.IsEmpty(expected) {

utils/safecast/boundary.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package safecast
22

3-
func greaterThanUpperBoundary[C1 IConvertable, C2 IConvertable](value C1, upperBoundary C2) (greater bool) {
3+
func greaterThanUpperBoundary[C1 IConvertible, C2 IConvertible](value C1, upperBoundary C2) (greater bool) {
44
if value <= 0 {
55
return
66
}
@@ -18,7 +18,7 @@ func greaterThanUpperBoundary[C1 IConvertable, C2 IConvertable](value C1, upperB
1818
return
1919
}
2020

21-
func lessThanLowerBoundary[T IConvertable, T2 IConvertable](value T, boundary T2) (lower bool) {
21+
func lessThanLowerBoundary[T IConvertible, T2 IConvertible](value T, boundary T2) (lower bool) {
2222
if value >= 0 {
2323
return
2424
}

utils/safecast/cast.go

Lines changed: 211 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,94 @@
11
package safecast
22

3-
import "math"
3+
import (
4+
"math"
45

5-
// ToInt attempts to convert any [IConvertable] value to an int.
6-
// If the conversion results in a value outside the range of an int,
7-
// the closest boundary value will be returned.
8-
func ToInt[C IConvertable](i C) int {
6+
"github.com/ARM-software/golang-utils/utils/commonerrors"
7+
"github.com/ARM-software/golang-utils/utils/ptr"
8+
)
9+
10+
// CastFunc defines a function that converts a value of type F to type T,
11+
// where both types satisfy [IConvertible].
12+
type CastFunc[F, T IConvertible] func(F) T
13+
14+
// Cast converts an [IConvertible] value to another [IConvertible] type
15+
// using the supplied conversion function.
16+
func Cast[F, T IConvertible](i F, f CastFunc[F, T]) T {
17+
return f(i)
18+
}
19+
20+
// RobustCast converts an [IConvertible] value to the target type T by
21+
// dispatching to the appropriate safe helper for T.
22+
//
23+
// Conversions to bounded integer and float32 types use the corresponding
24+
// helper, so out-of-range values are clamped to the nearest valid boundary.
25+
// If types are not supported, an error is returned.
26+
func RobustCast[F, T IConvertible](i F) (c T, err error) {
27+
var zero T
28+
29+
switch any(zero).(type) {
30+
case int:
31+
c = any(Cast(i, ToInt[F])).(T)
32+
case uint:
33+
c = any(Cast(i, ToUint[F])).(T)
34+
case int8:
35+
c = any(Cast(i, ToInt8[F])).(T)
36+
case uint8:
37+
c = any(Cast(i, ToUint8[F])).(T)
38+
case int16:
39+
c = any(Cast(i, ToInt16[F])).(T)
40+
case uint16:
41+
c = any(Cast(i, ToUint16[F])).(T)
42+
case int32:
43+
c = any(Cast(i, ToInt32[F])).(T)
44+
case uint32:
45+
c = any(Cast(i, ToUint32[F])).(T)
46+
case int64:
47+
c = any(Cast(i, ToInt64[F])).(T)
48+
case uint64:
49+
c = any(Cast(i, ToUint64[F])).(T)
50+
case float32:
51+
c = any(Cast(i, ToFloat32[F])).(T)
52+
case float64:
53+
c = any(Cast(i, ToFloat64[F])).(T)
54+
default:
55+
err = commonerrors.New(commonerrors.ErrUnsupported, "target type for casting is not supported")
56+
}
57+
return
58+
}
59+
60+
// CastRef converts a reference to an [IConvertible] value to a reference
61+
// to another [IConvertible] type using the supplied conversion function.
62+
// A nil input returns nil.
63+
func CastRef[F, T IConvertible](i *F, f CastFunc[F, T]) *T {
64+
if i == nil {
65+
return nil
66+
}
67+
return ptr.To(Cast[F, T](ptr.From(i), f))
68+
}
69+
70+
// RobustCastRef converts a reference to an [IConvertible] value to a reference
71+
// to the target type T by dispatching to the appropriate safe helper for T.
72+
// A nil input returns nil.
73+
// If types are not supported, an error is returned.
74+
func RobustCastRef[F, T IConvertible](i *F) (c *T, err error) {
75+
if i == nil {
76+
c = nil
77+
return
78+
}
79+
80+
v, err := RobustCast[F, T](ptr.From(i))
81+
if err != nil {
82+
return
83+
}
84+
c = ptr.To(v)
85+
return
86+
}
87+
88+
// ToInt attempts to convert an [IConvertible] value to an int.
89+
// If the converted value falls outside the range of int,
90+
// the nearest boundary value is returned instead.
91+
func ToInt[C IConvertible](i C) int {
992
if lessThanLowerBoundary(i, math.MinInt) {
1093
return math.MinInt
1194
}
@@ -15,10 +98,16 @@ func ToInt[C IConvertable](i C) int {
1598
return int(i)
1699
}
17100

18-
// ToUint attempts to convert any [IConvertable] value to an uint.
19-
// If the conversion results in a value outside the range of an uint,
20-
// the closest boundary value will be returned.
21-
func ToUint[C IConvertable](i C) uint {
101+
// ToIntRef attempts to convert a reference to an [IConvertible] value
102+
// to a reference to an int. A nil input returns nil.
103+
func ToIntRef[C IConvertible](i *C) *int {
104+
return CastRef(i, ToInt[C])
105+
}
106+
107+
// ToUint attempts to convert an [IConvertible] value to a uint.
108+
// If the converted value falls outside the range of uint,
109+
// the nearest boundary value is returned instead.
110+
func ToUint[C IConvertible](i C) uint {
22111
if lessThanLowerBoundary(i, uint(0)) {
23112
return 0
24113
}
@@ -28,10 +117,16 @@ func ToUint[C IConvertable](i C) uint {
28117
return uint(i)
29118
}
30119

31-
// ToInt8 attempts to convert any [IConvertable] value to an int8.
32-
// If the conversion results in a value outside the range of an int8,
33-
// the closest boundary value will be returned.
34-
func ToInt8[C IConvertable](i C) int8 {
120+
// ToUintRef attempts to convert a reference to an [IConvertible] value
121+
// to a reference to a uint. A nil input returns nil.
122+
func ToUintRef[C IConvertible](i *C) *uint {
123+
return CastRef(i, ToUint[C])
124+
}
125+
126+
// ToInt8 attempts to convert an [IConvertible] value to an int8.
127+
// If the converted value falls outside the range of int8,
128+
// the nearest boundary value is returned instead.
129+
func ToInt8[C IConvertible](i C) int8 {
35130
if lessThanLowerBoundary(i, math.MinInt8) {
36131
return math.MinInt8
37132
}
@@ -41,10 +136,16 @@ func ToInt8[C IConvertable](i C) int8 {
41136
return int8(i)
42137
}
43138

44-
// ToUint8 attempts to convert any [IConvertable] value to an uint8.
45-
// If the conversion results in a value outside the range of an uint8,
46-
// the closest boundary value will be returned.
47-
func ToUint8[C IConvertable](i C) uint8 {
139+
// ToInt8Ref attempts to convert a reference to an [IConvertible] value
140+
// to a reference to an int8. A nil input returns nil.
141+
func ToInt8Ref[C IConvertible](i *C) *int8 {
142+
return CastRef(i, ToInt8[C])
143+
}
144+
145+
// ToUint8 attempts to convert an [IConvertible] value to a uint8.
146+
// If the converted value falls outside the range of uint8,
147+
// the nearest boundary value is returned instead.
148+
func ToUint8[C IConvertible](i C) uint8 {
48149
if lessThanLowerBoundary(i, 0) {
49150
return 0
50151
}
@@ -54,10 +155,16 @@ func ToUint8[C IConvertable](i C) uint8 {
54155
return uint8(i)
55156
}
56157

57-
// ToInt16 attempts to convert any [IConvertable] value to an int16.
58-
// If the conversion results in a value outside the range of an int16,
59-
// the closest boundary value will be returned.
60-
func ToInt16[C IConvertable](i C) int16 {
158+
// ToUint8Ref attempts to convert a reference to an [IConvertible] value
159+
// to a reference to a uint8. A nil input returns nil.
160+
func ToUint8Ref[C IConvertible](i *C) *uint8 {
161+
return CastRef(i, ToUint8[C])
162+
}
163+
164+
// ToInt16 attempts to convert an [IConvertible] value to an int16.
165+
// If the converted value falls outside the range of int16,
166+
// the nearest boundary value is returned instead.
167+
func ToInt16[C IConvertible](i C) int16 {
61168
if lessThanLowerBoundary(i, math.MinInt16) {
62169
return math.MinInt16
63170
}
@@ -67,10 +174,16 @@ func ToInt16[C IConvertable](i C) int16 {
67174
return int16(i)
68175
}
69176

70-
// ToUint16 attempts to convert any [IConvertable] value to an uint16.
71-
// If the conversion results in a value outside the range of an uint16,
72-
// the closest boundary value will be returned.
73-
func ToUint16[C IConvertable](i C) uint16 {
177+
// ToInt16Ref attempts to convert a reference to an [IConvertible] value
178+
// to a reference to an int16. A nil input returns nil.
179+
func ToInt16Ref[C IConvertible](i *C) *int16 {
180+
return CastRef(i, ToInt16[C])
181+
}
182+
183+
// ToUint16 attempts to convert an [IConvertible] value to a uint16.
184+
// If the converted value falls outside the range of uint16,
185+
// the nearest boundary value is returned instead.
186+
func ToUint16[C IConvertible](i C) uint16 {
74187
if lessThanLowerBoundary(i, 0) {
75188
return 0
76189
}
@@ -80,10 +193,16 @@ func ToUint16[C IConvertable](i C) uint16 {
80193
return uint16(i)
81194
}
82195

83-
// ToInt32 attempts to convert any [IConvertable] value to an int32.
84-
// If the conversion results in a value outside the range of an int32,
85-
// the closest boundary value will be returned.
86-
func ToInt32[C IConvertable](i C) int32 {
196+
// ToUint16Ref attempts to convert a reference to an [IConvertible] value
197+
// to a reference to a uint16. A nil input returns nil.
198+
func ToUint16Ref[C IConvertible](i *C) *uint16 {
199+
return CastRef(i, ToUint16[C])
200+
}
201+
202+
// ToInt32 attempts to convert an [IConvertible] value to an int32.
203+
// If the converted value falls outside the range of int32,
204+
// the nearest boundary value is returned instead.
205+
func ToInt32[C IConvertible](i C) int32 {
87206
if lessThanLowerBoundary(i, math.MinInt32) {
88207
return math.MinInt32
89208
}
@@ -93,10 +212,16 @@ func ToInt32[C IConvertable](i C) int32 {
93212
return int32(i)
94213
}
95214

96-
// ToUint32 attempts to convert any [IConvertable] value to an uint32.
97-
// If the conversion results in a value outside the range of an uint32,
98-
// the closest boundary value will be returned.
99-
func ToUint32[C IConvertable](i C) uint32 {
215+
// ToInt32Ref attempts to convert a reference to an [IConvertible] value
216+
// to a reference to an int32. A nil input returns nil.
217+
func ToInt32Ref[C IConvertible](i *C) *int32 {
218+
return CastRef(i, ToInt32[C])
219+
}
220+
221+
// ToUint32 attempts to convert an [IConvertible] value to a uint32.
222+
// If the converted value falls outside the range of uint32,
223+
// the nearest boundary value is returned instead.
224+
func ToUint32[C IConvertible](i C) uint32 {
100225
if lessThanLowerBoundary(i, 0) {
101226
return 0
102227
}
@@ -106,10 +231,16 @@ func ToUint32[C IConvertable](i C) uint32 {
106231
return uint32(i)
107232
}
108233

109-
// ToInt64 attempts to convert any [IConvertable] value to an int64.
110-
// If the conversion results in a value outside the range of an int64,
111-
// the closest boundary value will be returned.
112-
func ToInt64[C IConvertable](i C) int64 {
234+
// ToUint32Ref attempts to convert a reference to an [IConvertible] value
235+
// to a reference to a uint32. A nil input returns nil.
236+
func ToUint32Ref[C IConvertible](i *C) *uint32 {
237+
return CastRef(i, ToUint32[C])
238+
}
239+
240+
// ToInt64 attempts to convert an [IConvertible] value to an int64.
241+
// If the converted value falls outside the range of int64,
242+
// the nearest boundary value is returned instead.
243+
func ToInt64[C IConvertible](i C) int64 {
113244
if lessThanLowerBoundary(i, math.MinInt64) {
114245
return math.MinInt64
115246
}
@@ -119,10 +250,16 @@ func ToInt64[C IConvertable](i C) int64 {
119250
return int64(i)
120251
}
121252

122-
// ToUint64 attempts to convert any [IConvertable] value to an uint64.
123-
// If the conversion results in a value outside the range of an uint64,
124-
// the closest boundary value will be returned.
125-
func ToUint64[C IConvertable](i C) uint64 {
253+
// ToInt64Ref attempts to convert a reference to an [IConvertible] value
254+
// to a reference to an int64. A nil input returns nil.
255+
func ToInt64Ref[C IConvertible](i *C) *int64 {
256+
return CastRef(i, ToInt64[C])
257+
}
258+
259+
// ToUint64 attempts to convert an [IConvertible] value to a uint64.
260+
// If the converted value falls outside the range of uint64,
261+
// the nearest boundary value is returned instead.
262+
func ToUint64[C IConvertible](i C) uint64 {
126263
if lessThanLowerBoundary(i, uint64(0)) {
127264
return 0
128265
}
@@ -132,7 +269,38 @@ func ToUint64[C IConvertable](i C) uint64 {
132269
return uint64(i)
133270
}
134271

135-
// ToFloat64 attempts to convert any [IConvertable] value to an float64.
136-
func ToFloat64[C IConvertable](i C) float64 {
272+
// ToUint64Ref attempts to convert a reference to an [IConvertible] value
273+
// to a reference to a uint64. A nil input returns nil.
274+
func ToUint64Ref[C IConvertible](i *C) *uint64 {
275+
return CastRef(i, ToUint64[C])
276+
}
277+
278+
// ToFloat64 attempts to convert an [IConvertible] value to a float64.
279+
func ToFloat64[C IConvertible](i C) float64 {
137280
return float64(i)
138281
}
282+
283+
// ToFloat64Ref attempts to convert a reference to an [IConvertible] value
284+
// to a reference to a float64. A nil input returns nil.
285+
func ToFloat64Ref[C IConvertible](i *C) *float64 {
286+
return CastRef(i, ToFloat64[C])
287+
}
288+
289+
// ToFloat32 attempts to convert an [IConvertible] value to a float32.
290+
// If the converted value falls outside the range of float32,
291+
// the nearest boundary value is returned instead.
292+
func ToFloat32[C IConvertible](i C) float32 {
293+
if lessThanLowerBoundary(i, -math.MaxFloat32) || math.IsInf(float64(i), -1) {
294+
return -math.MaxFloat32
295+
}
296+
if greaterThanUpperBoundary(i, math.MaxFloat32) || math.IsInf(float64(i), 1) {
297+
return math.MaxFloat32
298+
}
299+
return float32(i)
300+
}
301+
302+
// ToFloat32Ref attempts to convert a reference to an [IConvertible] value
303+
// to a reference to a float32. A nil input returns nil.
304+
func ToFloat32Ref[C IConvertible](i *C) *float32 {
305+
return CastRef(i, ToFloat32[C])
306+
}

0 commit comments

Comments
 (0)