{"id":13478644,"url":"https://github.com/skyzh/type-exercise-in-rust","last_synced_at":"2025-05-16T02:05:07.616Z","repository":{"id":38228564,"uuid":"450123586","full_name":"skyzh/type-exercise-in-rust","owner":"skyzh","description":"Learn Rust dark magics by implementing an expression framework in database systems","archived":false,"fork":false,"pushed_at":"2024-01-18T07:11:44.000Z","size":477,"stargazers_count":1413,"open_issues_count":1,"forks_count":63,"subscribers_count":21,"default_branch":"main","last_synced_at":"2025-05-16T02:04:27.147Z","etag":null,"topics":["database","rust","tutorial","type"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skyzh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-20T14:11:37.000Z","updated_at":"2025-05-13T10:09:33.000Z","dependencies_parsed_at":"2024-06-12T21:01:08.101Z","dependency_job_id":null,"html_url":"https://github.com/skyzh/type-exercise-in-rust","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Ftype-exercise-in-rust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Ftype-exercise-in-rust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Ftype-exercise-in-rust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Ftype-exercise-in-rust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skyzh","download_url":"https://codeload.github.com/skyzh/type-exercise-in-rust/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254453646,"owners_count":22073616,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","rust","tutorial","type"],"created_at":"2024-07-31T16:01:59.786Z","updated_at":"2025-05-16T02:05:07.554Z","avatar_url":"https://github.com/skyzh.png","language":"Rust","readme":"# Type Exercise in Rust\n\nThis is a short lecture on how to use the Rust type system to build necessary components in a database system.\n\nThe lecture evolves around how Rust programmers (like me) build database systems in the Rust programming language. We leverage the Rust type system to **minimize** runtime cost and make our development process easier with **safe**, **nightly** Rust.\n\nIn this tutorial, you will learn:\n\n* How to build an Arrow-like library with strong compile-time type. (Day 1 - 3)\n* How to use declarative macros to implement dispatch functions on a non-object-safe trait. (Day 4)\n* How to use GAT (generic associated types) and how to vectorize any scalar function with GAT generic parameter. (Day 5 - 6)\n  * ... how to by-pass compiler bugs on GAT lifetime in Fn trait.\n  * ... how to manually implement covariant on GAT lifetime.\n  * ... how to correctly add trait bounds for GAT.\n* How to use declarative macros to associate things together. (Day 7)\n\n![Map of Types](map-of-types.png)\n\n## Community\n\nYou may join skyzh's Discord server and study with the type-exercise community.\n\n[![Join skyzh's Discord Server](https://dcbadge.vercel.app/api/server/ZgXzxpua3H)](https://skyzh.dev/join/discord)\n\n## See Also...\n\n### RisingLight\n\n[RisingLight](https://github.com/risinglightdb/risinglight) is an OLAP database system for educational purpose. Most of the techniques described in this lecture have already been implemented in our educational database system “RisingLight”.\n\n### Databend\n\nDatabend's expression evaluation implementation is greatly influenced by type-exercise. You may see the implementation in [datavalues crate](https://github.com/datafuselabs/databend/blob/main/common/datavalues/src/scalars/mod.rs).\n\n### RisingWave\n\n[RisingWave](https://github.com/singularity-data/risingwave) is a cloud-native streaming database product. It is the first time that I experimented with GAT-related things in RisingWave to auto vectorize expressions. It applies almost the same techniques as described in this lecture.\n\n### TiKV Coprocessor\n\nI worked on TiKV two years ago on its expression evaluation framework. At the time of building [TiKV coprocessor](https://github.com/tikv/tikv/tree/master/components/tidb_query_expr), there is no GAT. TiKV coprocessor is an example of using procedural macro to unify behavior of different types of arrays, which is totally a different approach from this tutorial (but maybe in a more understandable manner). You may also take a look!\n\n### Related Issues in Rust Compiler\n\nDuring writing this tutorial, I found several confusing compile errors from the compiler. If all of them have been solved on the Rust side, we could have written GAT program easier!\n\n* https://github.com/rust-lang/rust/issues/93342\n* https://github.com/rust-lang/rust/issues/93340\n\n## Deep Dive Type Exercise Series (in Chinese)\n\nOn My Blog:\n* [Part 0 - Part 2](https://www.skyzh.dev/posts/articles/2022-01-22-rust-type-exercise-in-database-executors/)\n* [Part 3 - Part 6](https://www.skyzh.dev/posts/articles/2022-01-24-rust-type-exercise-in-database-executors-middle/)\n* [Part 7](https://www.skyzh.dev/posts/articles/2022-02-01-rust-type-exercise-in-database-executors-final/)\n\nOn Zhihu:\n* [Part 0: Intro](https://zhuanlan.zhihu.com/p/460702914)\n* [Part 1: Array and ArrayBuilder](https://zhuanlan.zhihu.com/p/460977012)\n* [Part 2: Scalar and ScalarRef](https://zhuanlan.zhihu.com/p/461405621)\n* [Part 3, 4: TryFrom and Macro Expansion](https://zhuanlan.zhihu.com/p/461657165)\n* [Part 5, 6: Bypassing GAT Compile Errors (or Compiler Bugs?)](https://zhuanlan.zhihu.com/p/461901665)\n* [Part 7: Associate Logical Types with Physical Types](https://zhuanlan.zhihu.com/p/463477290)\n\n## Day 1: `Array` and `ArrayBuilder`\n\n`ArrayBuilder` and `Array` are reciprocal traits. `ArrayBuilder` creates an `Array`, while we can create a new array\nusing `ArrayBuilder` with existing `Array`. In day 1, we implement arrays for primitive types (like `i32`, `f32`)\nand for variable-length types (like `String`). We use associated types in traits to deduce the right type in generic\nfunctions and use GAT to unify the `Array` interfaces for both fixed-length and variable-length types. This framework\nis also very similar to libraries like Apache Arrow, but with much stronger type constraints and much lower runtime\noverhead.\n\nThe special thing is that, we use blanket implementation for `i32` and `f32` arrays -- `PrimitiveArray\u003cT\u003e`. This would\nmake our journey much more challenging, as we need to carefully evaluate the trait bounds needed for them in the\nfollowing days.\n\n### Goals\n\nDevelopers can create generic functions over all types of arrays -- no matter fixed-length primitive array like\n`I32Array`, or variable-length array like `StringArray`.\n\nWithout our `Array` trait, developers might to implement...\n\n```rust\nfn build_i32_array_from_vec(items: \u0026[Option\u003ci32\u003e]) -\u003e Vec\u003ci32\u003e { /* .. */ }\nfn build_str_array_from_vec(items: \u0026[Option\u003c\u0026str\u003e]) -\u003e Vec\u003cString\u003e { /* .. */ }\n```\n\nNote that the function takes different parameter -- one `i32` without lifetime, one `\u0026str`. Our `Array` trait\ncan unify their behavior:\n\n```rust\nfn build_array_from_vec\u003cA: Array\u003e(items: \u0026[Option\u003cA::RefItem\u003c'_\u003e\u003e]) -\u003e A {\n    let mut builder = A::Builder::with_capacity(items.len());\n    for item in items {\n        builder.push(*item);\n    }\n    builder.finish()\n}\n\n#[test]\nfn test_build_int32_array() {\n    let data = vec![Some(1), Some(2), Some(3), None, Some(5)];\n    let array = build_array_from_vec::\u003cI32Array\u003e(\u0026data[..]);\n}\n\n#[test]\nfn test_build_string_array() {\n    let data = vec![Some(\"1\"), Some(\"2\"), Some(\"3\"), None, Some(\"5\"), Some(\"\")];\n    let array = build_array_from_vec::\u003cStringArray\u003e(\u0026data[..]);\n}\n```\n\nAlso, we will be able to implement an `ArrayIterator` for all types of `Array`s.\n\n## Day 2: `Scalar` and `ScalarRef`\n\n`Scalar` and `ScalarRef` are reciprocal types. We can get a reference `ScalarRef` of a `Scalar`, and convert\n`ScalarRef` back to `Scalar`. By adding these two traits, we can write more generic functions with zero runtime\noverhead on type matching and conversion. Meanwhile, we associate `Scalar` with `Array`, so as to write functions\nmore easily.\n\n### Goals\n\nWithout our `Scalar` implement, there could be problems:\n\n```rust\nfn build_array_repeated_owned\u003cA: Array\u003e(item: A::OwnedItem, len: usize) -\u003e A {\n    let mut builder = A::Builder::with_capacity(len);\n    for _ in 0..len {\n        builder.push(Some(item /* How to convert `item` to `RefItem`? */));\n    }\n    builder.finish()\n}\n```\n\nWith `Scalar` trait and corresponding implements,\n\n```rust\nfn build_array_repeated_owned\u003cA: Array\u003e(item: A::OwnedItem, len: usize) -\u003e A {\n    let mut builder = A::Builder::with_capacity(len);\n    for _ in 0..len {\n        builder.push(Some(item.as_scalar_ref())); // Now we have `as_scalar_ref` on `Scalar`!\n    }\n    builder.finish()\n}\n```\n\n## Day 3: `ArrayImpl`, `ArrayBuilderImpl`, `ScalarImpl` and `ScalarRefImpl`\n\nIt could be possible that some information is not available until runtime. Therefore, we use `XXXImpl` enums to\ncover all variants of a single type. At the same time, we also add `TryFrom\u003cArrayImpl\u003e` and `Into\u003cArrayImpl\u003e`\nbound for `Array`.\n\n### Goals\n\nThis is hard -- imagine we simply require `TryFrom\u003cArrayImpl\u003e` and `Into\u003cArrayImpl\u003e` bound on `Array`:\n\n```rust\npub trait Array:\n    Send + Sync + Sized + 'static + TryFrom\u003cArrayImpl\u003e + Into\u003cArrayImpl\u003e\n```\n\nCompiler will complain:\n\n```\n43 | impl\u003cT\u003e Array for PrimitiveArray\u003cT\u003e\n   |         ^^^^^ the trait `From\u003cPrimitiveArray\u003cT\u003e\u003e` is not implemented for `array::ArrayImpl`\n   |\n   = note: required because of the requirements on the impl of `Into\u003carray::ArrayImpl\u003e` for `PrimitiveArray\u003cT\u003e`\n```\n\nThis is because we use blanket implementation for `PrimitiveArray` to cover all primitive types. In day 3,\nwe learn how to correctly add bounds to `PrimitiveArray`.\n\n## Day 4: More Types and Methods with Macro\n\n`ArrayImpl` should supports common functions in traits, but `Array` trait doesn't have a unified interface for\nall types -- `I32Array` accepts `get(\u0026self, idx: usize) -\u003e Option\u003ci32\u003e` while `StringArray` accepts\n`get(\u0026self, idx: usize) -\u003e \u0026str`. We need a `get(\u0026self, idx:usize) -\u003e ScalarRefImpl\u003c'_\u003e` on `ArrayImpl`. Therefore,\nwe have to write the match arms to dispatch the methods.\n\nAlso, we have written so many boilerplate code for `From` and `TryFrom`. We need to eliminate such duplicated code.\n\nAs we are having more and more data types, we need to write the same code multiple times within a match arm. In\nday 4, we use declarative macros (instead of procedural macros or other kinds of code generator) to generate such\ncode and avoid writing boilerplate code.\n\n### Goals\n\nBefore that, we need to implement every `TryFrom` or `Scalar` by ourselves:\n\n```rust\nimpl\u003c'a\u003e ScalarRef\u003c'a\u003e for i32 {\n    type ArrayType = I32Array;\n    type ScalarType = i32;\n\n    fn to_owned_scalar(\u0026self) -\u003e i32 {\n        *self\n    }\n}\n\n// repeat the same code fore i64, f64, ...\n```\n\n```rust\nimpl ArrayImpl {\n    /// Get the value at the given index.\n    pub fn get(\u0026self, idx: usize) -\u003e Option\u003cScalarRefImpl\u003c'_\u003e\u003e {\n        match self {\n            Self::Int32(array) =\u003e array.get(idx).map(ScalarRefImpl::Int32),\n            Self::Float64(array) =\u003e array.get(idx).map(ScalarRefImpl::Float64),\n            // ...\n            // repeat the types for every functions we added on `Array`\n        }\n    }\n```\n\nWith macros, we can easily add more and more types. In day 4, we will support:\n\n```rust\npub enum ArrayImpl {\n    Int16(I16Array),\n    Int32(I32Array),\n    Int64(I64Array),\n    Float32(F32Array),\n    Float64(F64Array),\n    Bool(BoolArray),\n    String(StringArray),\n}\n```\n\nWith little code changed and eliminating boilerplate code.\n\n## Day 5: Binary Expressions\n\nNow that we have `Array`, `ArrayBuilder`, `Scalar` and `ScalarRef`, we can convert every function we wrote to a\nvectorized one using generics.\n\n### Goals\n\nDevelopers will only need to implement:\n\n```rust\npub fn str_contains(i1: \u0026str, i2: \u0026str) -\u003e bool {\n    i1.contains(i2)\n}\n```\n\nAnd they can create `BinaryExpression` around this function with any type:\n\n```rust\n// Vectorize `str_contains` to accept an array instead of a single value.\nlet expr = BinaryExpression::\u003cStringArray, StringArray, BoolArray, _\u003e::new(str_contains);\n// We only need to pass `ArrayImpl` to the expression, and it will do everything for us,\n// including type checks, loopping, etc.\nlet result = expr\n    .eval(\n        \u0026StringArray::from_slice(\u0026[Some(\"000\"), Some(\"111\"), None]).into(),\n        \u0026StringArray::from_slice(\u0026[Some(\"0\"), Some(\"0\"), None]).into(),\n    )\n    .unwrap();\n```\n\nDevelopers can even create `BinaryExpression` around generic functions:\n\n```rust\npub fn cmp_le\u003c'a, I1: Array, I2: Array, C: Array + 'static\u003e(\n    i1: I1::RefItem\u003c'a\u003e,\n    i2: I2::RefItem\u003c'a\u003e,\n) -\u003e bool\nwhere\n    I1::RefItem\u003c'a\u003e: Into\u003cC::RefItem\u003c'a\u003e\u003e,\n    I2::RefItem\u003c'a\u003e: Into\u003cC::RefItem\u003c'a\u003e\u003e,\n    C::RefItem\u003c'a\u003e: PartialOrd,\n{\n    i1.into().partial_cmp(\u0026i2.into()).unwrap() == Ordering::Less\n}\n```\n\n\n```rust\n// Vectorize `cmp_le` to accept an array instead of a single value.\nlet expr = BinaryExpression::\u003cI32Array, I32Array, BoolArray, _\u003e::new(\n        cmp_le::\u003cI32Array, I32Array, I64Array\u003e,\n    );\nlet result: ArrayImpl = expr.eval(ArrayImpl, ArrayImpl).unwrap();\n\n// `cmp_le` can also be used on `\u0026str`.\nlet expr = BinaryExpression::\u003cStringArray, StringArray, BoolArray, _\u003e::new(\n        cmp_le::\u003cStringArray, StringArray, StringArray\u003e,\n    );\nlet result: ArrayImpl = expr.eval(ArrayImpl, ArrayImpl).unwrap();\n```\n\n## Day 6: Erase Expression Lifetime\n\nVectorization looks fancy in the implementation in day 5, but there is a critical flaw -- `BinaryExpression`\ncan only process `\u0026'a ArrayImpl` instead of for any lifetime.\n\n```rust\nimpl\u003c'a, I1: Array, I2: Array, O: Array, F\u003e BinaryExpression\u003cI1, I2, O, F\u003e {\n    pub fn eval(\u0026self, i1: \u0026'a ArrayImpl, i2: \u0026'a ArrayImpl) -\u003e Result\u003cArrayImpl\u003e {\n        // ...\n    }\n}\n```\n\nIn day 6, we erase the expression lifetime by defining a `BinaryExprFunc` trait and implements it for all expression\nfunctions. The `BinaryExpression` will be implemented as follows:\n\n```rust\nimpl\u003cI1: Array, I2: Array, O: Array, F\u003e BinaryExpression\u003cI1, I2, O, F\u003e {\n    pub fn eval(\u0026self, i1: \u0026ArrayImpl, i2: \u0026ArrayImpl) -\u003e Result\u003cArrayImpl\u003e {\n        // ...\n    }\n}\n```\n\nAnd there will be an `Expression` trait which can be made into a trait object:\n\n```rust\npub trait Expression {\n    /// Evaluate an expression with run-time number of [`ArrayImpl`]s.\n    fn eval_expr(\u0026self, data: \u0026[\u0026ArrayImpl]) -\u003e Result\u003cArrayImpl\u003e;\n}\n```\n\nIn this day, we have two solutions -- the hard way and the easy way.\n\n### Goals -- The Easy Way\n\nIf we make each scalar function into a struct, things will be a lot easier.\n\nDevelopers will now implement scalar function as follows:\n\n```rust\npub struct ExprStrContains;\n\nimpl BinaryExprFunc\u003cStringArray, StringArray, BoolArray\u003e for ExprStrContains {\n    fn eval(\u0026self, i1: \u0026str, i2: \u0026str) -\u003e bool {\n        i1.contains(i2)\n    }\n}\n```\n\nAnd now we can have an expression trait over all expression, with all type and lifetime erased:\n\n```rust\npub trait Expression {\n    /// Evaluate an expression with run-time number of [`ArrayImpl`]s.\n    fn eval_expr(\u0026self, data: \u0026[\u0026ArrayImpl]) -\u003e Result\u003cArrayImpl\u003e;\n}\n```\n\n`Expression` can be made into a `Box\u003cdyn Expression\u003e`, therefore being used in building expressions at runtime.\n\n### ~~Goals -- The Hard Way~~\n\nAs [rust-lang/rust #90087](https://github.com/rust-lang/rust/pull/90887) lands, the compiler bugs have been fixed. So we don't need to do any extra work for this day to support function expressions. All `BinaryExprFunc` can be replaced with `F: Fn(I1::RefType\u003c'_\u003e, I2::RefType\u003c'_\u003e) -\u003e O`.\n\nIn the hard way chapter, we will dive into the black magics and fight against (probably) compiler bugs, so as\nto make function vectorization look very approachable to SQL function developers.\n\nTo begin with, we will change the signature of `BinaryExpression` to take `Scalar` as parameter:\n\n```rust\npub struct BinaryExpression\u003cI1: Scalar, I2: Scalar, O: Scalar, F\u003e {\n    func: F,\n    _phantom: PhantomData\u003c(I1, I2, O)\u003e,\n}\n```\n\nThen we will do a lot of black magics on `Scalar` type, so as to do the conversion freely between `Array::RefItem`\nand `Scalar::RefType`. This will help us bypass most of the issues in GAT, and yields the following vectorization\ncode:\n\n```rust\nbuilder.push(Some(O::cast_s_to_a(\n    self.func\n        .eval(I1::cast_a_to_s(i1), I2::cast_a_to_s(i2))\n        .as_scalar_ref(),\n)))\n```\n\nWe will construct a bridge trait `BinaryExprFunc` between plain functions and the one that can be used by\n`BinaryExpression`.\n\nAnd finally developers can simply write a function and supply it to `BinaryExpression`.\n\n```rust\nlet expr = BinaryExpression::\u003cString, String, bool, _\u003e::new(str_contains);\n```\n\n... or even with lambda functions:\n\n```rust\nlet expr = BinaryExpression::\u003cString, String, bool, _\u003e::new(|x1: \u0026str, x2: \u0026str| x1.contains(x2));\n```\n\n## Day 7: Physical Data Type and Logical Data Type\n\n`i32`, `i64` is simply physical types -- how types are stored in memory (or on disk). But in a database system,\nwe also have logical types (like `Char`, and `Varchar`). In day 7, we learn how to associate logical types with\nphysical types using macros.\n\n### Goals\n\nGoing back to our `build_binary_expression` function,\n\n```rust\n/// Build expression with runtime information.\npub fn build_binary_expression(\n    f: ExpressionFunc,\n) -\u003e Box\u003cdyn Expression\u003e {\n    match f {\n        CmpLe =\u003e Box::new(BinaryExpression::\u003cI32Array, I32Array, BoolArray, _\u003e::new(\n            ExprCmpLe::\u003c_, _, I32Array\u003e(PhantomData),\n        )),\n    /* ... */\n```\n\nCurrently, we only support `i32 \u003c i32` for `CmpLe` expression. We should be able to support cross-type comparison here.\n\n```rust\n/// Build expression with runtime information.\npub fn build_binary_expression(\n    f: ExpressionFunc,\n    i1: DataType,\n    i2: DataType\n) -\u003e Box\u003cdyn Expression\u003e {\n    match f {\n        CmpLe =\u003e match (i1, i2) {\n            (SmallInt, SmallInt) =\u003e /* I16Array, I16Array */,\n            (SmallInt, Real) =\u003e /* I16Array, Float32, cast to Float64 before comparison */,\n            /* ... */\n        }\n    /* ... */\n```\n\nWe have so many combinations of cross-type comparison, and we couldn't write them all by-hand. In day 7, we use\nmacros to associate logical data type with `Array` traits, and reduce the complexity of writing such functions.\n\n**Goals -- The Easy Way**\n\n```rust\n/// Build expression with runtime information.\npub fn build_binary_expression(\n    f: ExpressionFunc,\n    i1: DataType,\n    i2: DataType,\n) -\u003e Box\u003cdyn Expression\u003e {\n    use ExpressionFunc::*;\n\n    use crate::array::*;\n    use crate::expr::cmp::*;\n    use crate::expr::string::*;\n\n    match f {\n        CmpLe =\u003e for_all_cmp_combinations! { impl_cmp_expression_of, i1, i2, ExprCmpLe },\n        CmpGe =\u003e for_all_cmp_combinations! { impl_cmp_expression_of, i1, i2, ExprCmpGe },\n        CmpEq =\u003e for_all_cmp_combinations! { impl_cmp_expression_of, i1, i2, ExprCmpEq },\n        CmpNe =\u003e for_all_cmp_combinations! { impl_cmp_expression_of, i1, i2, ExprCmpNe },\n        StrContains =\u003e Box::new(\n            BinaryExpression::\u003cStringArray, StringArray, BoolArray, _\u003e::new(ExprStrContains),\n        ),\n    }\n}\n```\n\n**Goals -- The Hard Way**\n\n```rust\n/// Build expression with runtime information.\npub fn build_binary_expression(\n    f: ExpressionFunc,\n    i1: DataType,\n    i2: DataType,\n) -\u003e Box\u003cdyn Expression\u003e {\n    use ExpressionFunc::*;\n\n    use crate::expr::cmp::*;\n    use crate::expr::string::*;\n\n    match f {\n        CmpLe =\u003e for_all_cmp_combinations! { impl_cmp_expression_of, i1, i2, cmp_le },\n        CmpGe =\u003e for_all_cmp_combinations! { impl_cmp_expression_of, i1, i2, cmp_ge },\n        CmpEq =\u003e for_all_cmp_combinations! { impl_cmp_expression_of, i1, i2, cmp_eq },\n        CmpNe =\u003e for_all_cmp_combinations! { impl_cmp_expression_of, i1, i2, cmp_ne },\n        StrContains =\u003e Box::new(BinaryExpression::\u003cString, String, bool, _\u003e::new(\n            str_contains,\n        )),\n    }\n}\n```\n\nThe goal is to write as less code as possible to generate all combinations of comparison.\n\n## Day 8: List Type\n\nIn Apache Arrow, we have `ListArray`, which is equivalent to `Vec\u003cOption\u003cVec\u003cOption\u003cT\u003e\u003e\u003e\u003e`. We implement this in\nday 8.\n\n```rust\nlet mut builder = ListArrayBuilder::with_capacity(0);\nbuilder.push(Some((\u0026/* Some ArrayImpl */).into()));\nbuilder.push(Some((\u0026/* Some ArrayImpl */).into()));\nbuilder.push(None);\nbuilder.finish();\n```\n\n## Day 9: Boxed Array\n\nUse `Box\u003cdyn Array\u003e` instead of `ArrayImpl` enum.\n\nTo make as few modifications as possible to the current codebase, we add two traits:\n\n* `PhysicalTypeOf`: gets the physical type out of Array.\n* `DynArray`: the object safe trait for Array.\n\nThen, we can have `pub struct BoxedArray(Box\u003cdyn DynArray\u003e);` for dynamic dispatch.\n\n## Day 10: Expression Framework\n\nNow we are having more and more expression kinds, and we need an expression framework to unify them -- including\nunary, binary and expressions of more inputs.\n\nAt the same time, we will also experiment with return value optimizations in variable-size types.\n\n# TBD Lectures\n\n## Day 11: Aggregators\n\nAggregators are another kind of expressions. We learn how to implement them easily with our type system in day 10.\n","funding_links":[],"categories":["Rust"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyzh%2Ftype-exercise-in-rust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskyzh%2Ftype-exercise-in-rust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyzh%2Ftype-exercise-in-rust/lists"}