一个由Rust原生类型数组向Apache Arrow Array类型转换的库
前言
刚从C++转到Rust时,总觉得Rust语法实在是太丑了
写了一段时间Rust后,发现Rust语法确实丑
不过只要能过编译,能过测试(逻辑正确),就很难出问题,这是一个很大的优点
当然,时不时要重构也是必要的代价了
作为练手任务,mt预计时间为两周,实际的实现时间大概一周多一点,感谢无敌的Claude Code
结果缝缝补补,明确需求,重构,又花了一周,难绷
设计思路
mt给的简单框架,我们需要在运行时转换,从而需要一些类型擦除和分发的代码
具体来说,我们需要实现类似以下的东西
pub trait NativeArray {
type Item;
type ItemRef<'r>;
fn push(&mut self, item: Self::Item);
fn get(&self, index: usize) -> Self::ItemRef<'_>;
fn to_arrow_array(&self) -> arrow::Array;
}
/// Vec<i32> -> Int32Array
impl NativeArray for Vec<i32> {
...
fn to_arrow_array(&self) -> arrow::Array {
Int32Builder::new().append_slice(self.as_slice()).finish()
}
}
pub enum DynScalar {
Bool(bool),
Int32(i32),
String(String),
Binary(Vec<u8>),
List(Vec<DynScalar>),
}
pub struct DynNativeArray {
}
实现过程
静态部分
不过claude给的思路不太一样,它是对每一个type实现一个TypedVec,然后对这些Vec impl NativeArray 和 DynNativeArray,从而达到转换
由于arrow-rs已经对不少类型实现了Into这个trait,我们可以直接into()过去,因此静态部分实际上挺简单的,一天之内我就搓完了
基本类型直接用宏一把梭,就是需要对bool,string,binary,decimal和time类型再实现一下
动态部分
mt给的思路是用一个inner,加上自己写vtable分发来实现DynNativeArray
不过这里不是这样,而是用<Self as NativeArray>::to_arrow_array()这种神秘方法实现
要求构造时可以通过vector直接构造,所以需要DynScalar作为擦除,和dispatch代码
复杂度是不会消失的,对每种类型还是得老实写分发代码和实现trait,这里用了两天,但其实也不难
嵌套类型
按道理这个不是和前两个并列的,但是实现时是并列的,所以还是这样写
首先我们需要自己定义struct和DynScalar了,然后我们需要把前两部分的东西再写一遍
还要手写构造和转换代码,然后终于到了紧张刺激的debug环节
好在有原生的builder可以直接assert,哪里不对点哪里就完事了,这里又用了两天,完成了元素为基本类型情况下的转换
但是还有一(两)个大坑:
- 如何将含原生类型元素的嵌套类型转换为DynScalar?
- 如何使用schema(在不使用反射的情况下)转换struct?
上面的坑解决方法是这样的:
用过程宏来为编译期定义的Struct impl一个到DynScalar::Struct(HashMap<String,DynScalar>)的trait,再用函数将Vec
第二步转换时是要递归的,来解决嵌套问题
好在复杂度基本上在第二步,所以这个过程宏还好
加上mt觉得要加上测试深层嵌套的代码,于是又花了两天修
然后我觉得既然都在编译期确定了Struct,可以直接一步到位,于是又花了快两天写了个直接转换的过程宏
这个就复杂多了,只能靠展开来debug,还不确定是不是都测试到了
结果发现需要在运行时确定schema和数据类型,不一定有struct,直接从HashMap转,所以上面的两个过程宏疑似白写(
Option类型
这个其实没必要并列,不过还是写一下
我花了一天把原版的全部复刻了一遍,但是我觉得太丑了
然后review的时候觉得可以套用Arrow的布局,直接把nulls和offsets(如有)从转换时计算移到自己的field里,于是又花了一天合并回去
There is a negligible beginning in all great action and thought.

浙公网安备 33010602011771号