
lo 库 WithoutEmpty 函数解析过滤零值元素及其与 Compact 的关系【免费下载链接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo在 Go 项目中处理集合数据时去掉所有空值/零值元素是一个高频需求清理用户输入中混入的空字符串、过滤结果集里的 nil 指针、剔除数值切片中的 0。samber/lo是一个基于 Go 1.18 泛型的 Lodash 风格工具库其核心包提供了WithoutEmpty这一函数来完成此类过滤。本文以文档页 docs/data/core-withoutempty.md 为蓝本结合 intersect.go 与 slice.go 中的真实源码和测试用例讲清WithoutEmpty的行为边界、实现细节以及它与推荐替代品Compact的继承关系帮助你在编写 Go 切片清洗代码时做出正确的选型。函数签名与基本行为WithoutEmpty属于 lo 核心包的 intersect集合运算子分类文档页中记录的完整签名为func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice其语义是返回一个排除了所有零值zero value元素的新切片。参数约束有两点值得注意T comparable元素类型必须可比较能使用/!这是判断是否为零值的前提Slice ~[]T参数是一个可转换为[]T的命名切片类型返回值会保持与入参相同的切片类型这一点有专门的测试用例验证见下文。文档页给出的最简示例lo.WithoutEmpty([]int{0, 2, 10}) // []int{2, 10}重要提示文档明确标注该函数已被标记为弃用Deprecated官方建议使用 Compact 代替。源码注释同样写明了这一点在 intersect.go 中可以看到// WithoutEmpty returns a slice excluding zero values. // // Deprecated: Use lo.Compact instead. // Play: https://go.dev/play/p/iZvYJWuniJm func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice { return Compact(collection) }也就是说WithoutEmpty在实现上是一个直接委托给Compact的薄封装——两者行为完全等价WithoutEmpty保留只是为了向后兼容既有代码。新代码建议直接使用lo.Compact。底层实现Compact 的零值判定逻辑要理解WithoutEmpty过滤了什么需要看它真正调用的 Compact 实现// Compact returns a slice of all non-zero elements. func Compact[T comparable, Slice ~[]T](collection Slice) Slice { var zero T result : make(Slice, 0, len(collection)) for i : range collection { if collection[i] ! zero { result append(result, collection[i]) } } return result }实现逻辑非常直白var zero T声明一个T类型的零值变量作为比较基准预分配容量为len(collection)的结果切片逐元素与原零值做!比较非零值元素被保留返回全新切片不修改原切片lo 库的函数普遍遵循这一纯函数式风格。由于基准是 Go 类型系统的零值什么算空完全取决于元素类型元素类型被过滤掉的值int/float64等数值0stringboolfalse结构体所有字段均为零值的结构体*T指针nil注意指向零值结构体的非 nil 指针不会被过滤这个边界在测试中有明确验证。slice_test.go 的TestCompact用两个子测试覆盖了结构体场景// slice of structs // If all fields of an element are zero values, Compact removes it. r4 : Compact([]foo{ {bar: 1, baz: a}, // all fields are non-zero values {bar: 0, baz: }, // all fields are zero values {bar: 2, baz: }, // bar is non-zero }) // 结果{bar: 1, baz: a}, {bar: 2, baz: } // slice of pointers to structs // If an element is nil, Compact removes it. e1, e2, e3 : foo{bar: 1, baz: a}, foo{bar: 0, baz: }, foo{bar: 2, baz: } // NOTE: e2 is a zero value of foo, but its pointer e2 is not a zero value of *foo. r5 : Compact([]*foo{e1, e2, nil, e3}) // 结果[]*foo{e1, e2, e3}特别要注意第二条注释e2是一个非 nil指针即使它指向的结构体本身是全零值也不会被移除。如果你的业务语义是指向零值结构体的指针也算空需要自己用lo.Filter之类的谓词过滤函数处理而不能依赖WithoutEmpty/Compact。行为验证测试用例覆盖了哪些场景intersect_test.go 中的TestWithoutEmpty直接验证了该函数本身的行为包含三组表驱动用例和两个独立子测试tests : []struct { name string input []int expected []int }{ {name: removes leading zero, input: []int{0, 1, 2}, expected: []int{1, 2}}, {name: no zero to remove, input: []int{1, 2}, expected: []int{1, 2}}, {name: empty input, input: []int{}, expected: []int{}}, }移除零值[]int{0, 1, 2}→[]int{1, 2}无零值时原样返回内容[]int{1, 2}内容不变注意返回值是新建切片而非同一底层数组空输入空切片进空切片出nil 指针过滤WithoutEmpty([]*int{ToPtr(0), ToPtr(1), nil, ToPtr(2)})得到去掉nil后的三个指针——并且指向0的ToPtr(0)被保留与 Compact 测试中非 nil 指针不过滤的语义一致切片类型保持对自定义切片类型type myStrings []string调用后返回值的静态类型仍然是myStrings测试用is.IsType(nonempty, allStrings, type preserved)断言。这正是签名中Slice ~[]T约束带来的实用特性你可以在自己项目里定义type UserIDs []int这类类型过滤后依然能赋回原类型变量无需再次转换。同类函数选型什么时候用哪个WithoutEmpty文档页的 frontmatter 中列出了similarHelpers相近函数Compact、Without、WithoutBy。它们都定义在 intersect.go 中适合放在一起对比函数签名要点用途WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice无额外参数去除零值元素已弃用等价于CompactCompact[T comparable, Slice ~[]T](collection Slice) Slice无额外参数去除零值元素slice.go 中的正式实现推荐直接使用Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice变参 exclude 列表去除指定值不是零值见 intersect.goWithoutBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K, exclude ...K) Slice键提取函数 exclude 列表按提取出的键排除元素如按用户的ID字段批量剔除WithoutNth[T any, Slice ~[]T](collection Slice, nths ...int) Slice变参下标按下标位置剔除元素见 intersect.go简单决策路径想清理0 / 空串 / false / nil / 全零结构体这类类型零值→ 用lo.Compact旧代码中的lo.WithoutEmpty继续可用想剔除具体几个值比如[]int{1, 2, 3}去掉2→ 用lo.Without(slice, 2)想按属性键批量剔除比如从订单列表中排除若干OrderID→ 用lo.WithoutBy或lo.WithoutByErrintersect.go。从源码结构看Without和WithoutBy内部还做了一个性能相关的分支当 exclude 列表长度不超过withoutSmallExcludeThreshold 4intersect.go时走线性扫描的withoutSmall否则用Keyify构建哈希集合走withoutLarge。这个阈值常量旁的注释说明变参调用绝大多数只传 1~4 个值此时构建 map 的分配和哈希开销反而大于几次比较。虽然这与WithoutEmpty本身无关但体现了 lo 在同文件内对各集合函数的整体优化思路。使用前提与注意事项Go 版本要求lo 使用 Go 1.18 泛型go.mod 声明go 1.18WithoutEmpty依赖T comparable约束因此项目需使用 Go 1.18 或更高版本。元素必须可比较T comparable意味着元素类型不能是切片、map 或函数类型等不可比较类型结构体也要求所有字段可比较。零值语义由类型决定false、0、、nil、全零结构体都会被过滤。如果你希望0是合法数据例如计数、评分不要用WithoutEmpty/Compact应改用带谓词的lo.Filter按业务语义过滤。返回新切片该函数不修改入参返回一个新建切片对空切片输入会返回长度 0 的新切片。弃用状态WithoutEmpty已被标记Deprecated: Use lo.Compact instead。阅读第三方旧代码或本项目历史版本时遇到它可以直接理解为Compact的别名在新代码中请使用lo.Compact。小结WithoutEmpty是 lo 库 intersect 分类下用于剔除零值元素的工具函数签名func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice典型用法如lo.WithoutEmpty([]int{0, 2, 10}) // []int{2, 10}。从 intersect.go 源码确认它内部完全委托给 Compact官方已建议迁移至Compact。intersect_test.go 与 slice_test.go 的测试共同界定了其行为边界零值按 Go 类型语义判定、nil 指针被过滤而非指向零值结构体的非 nil 指针、入参切片类型在返回值上保持。理解了这些细节后你可以在去零值Compact/ 去指定值Without/ 按键剔除WithoutBy之间做出准确选型。【免费下载链接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考