Repository: jorgecarleitao/arrow2 (rust)

Issue:
OffsetBuffer Slice is Unsound
Noticed whilst working on #1446 

`OffsetBuffer::slice` does not verify that the length is non-zero, which results in UB from methods such as OffsetBuffer::first

```
#[test]
fn test_offsets_slice() {
    let mut buffer = OffsetsBuffer::<i32>::new();
    buffer.slice(0, 0);
    buffer.first();
}
```

Fails in debug with

```
  process didn't exit successfully: `/home/raphael/repos/external/arrow2/target/debug/deps/it-04058e53dc1ec684 'array::utf8::test_offsets_slice' --format=json --exact -Z unstable-options --show-output` (signal: 4, SIGILL: illegal instruction)
```


Pull request description:
Fixed missing assert len in `OffsetBuffer::slice`
fixes #1447

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 4b29ddf9a2..0be6ca402e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -101,8 +101,10 @@ odbc-api = { version = "0.36", optional = true }
 ahash = "0.8"
 
 # Support conversion to/from arrow-rs
-arrow-buffer = { version = "35.0.0", optional = true }
-arrow-schema = { version = "35.0.0", optional = true }
+arrow-buffer = { version = "36.0.0", optional = true }
+arrow-schema = { version = "36.0.0", optional = true }
+arrow-data = { version = "36.0.0", optional = true }
+arrow-array = { version = "36.0.0", optional = true }
 
 [target.wasm32-unknown-unknown.dependencies]
 getrandom = { version = "0.2", features = ["js"] }
@@ -159,7 +161,7 @@ full = [
     # parses timezones used in timestamp conversions
     "chrono-tz",
 ]
-arrow = ["arrow-buffer", "arrow-schema"]
+arrow = ["arrow-buffer", "arrow-schema", "arrow-data", "arrow-array"]
 io_odbc = ["odbc-api"]
 io_csv = ["io_csv_read", "io_csv_write"]
 io_csv_async = ["io_csv_read_async"]
diff --git a/src/array/binary/data.rs b/src/array/binary/data.rs
new file mode 100644
index 0000000000..57ce5ff671
--- /dev/null
+++ b/src/array/binary/data.rs
@@ -0,0 +1,42 @@
+use crate::array::{Arrow2Arrow, BinaryArray};
+use crate::bitmap::Bitmap;
+use crate::offset::{Offset, OffsetsBuffer};
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl<O: Offset> Arrow2Arrow for BinaryArray<O> {
+    fn to_data(&self) -> ArrayData {
+        let data_type = self.data_type.clone().into();
+        let builder = ArrayDataBuilder::new(data_type)
+            .len(self.offsets().len_proxy())
+            .buffers(vec![
+                self.offsets.clone().into_inner().into(),
+                self.values.clone().into(),
+            ])
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()));
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type = data.data_type().clone().into();
+
+        if data.is_empty() {
+            // Handle empty offsets
+            return Self::new_empty(data_type);
+        }
+
+        let buffers = data.buffers();
+
+        // Safety: ArrayData is valid
+        let mut offsets = unsafe { OffsetsBuffer::new_unchecked(buffers[0].clone().into()) };
+        offsets.slice(data.offset(), data.len() + 1);
+
+        Self {
+            data_type,
+            offsets,
+            values: buffers[1].clone().into(),
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/binary/mod.rs b/src/array/binary/mod.rs
index c6d4fce470..b08e78dc6d 100644
--- a/src/array/binary/mod.rs
+++ b/src/array/binary/mod.rs
@@ -24,6 +24,9 @@ pub use mutable_values::*;
 mod mutable;
 pub use mutable::*;
 
+#[cfg(feature = "arrow")]
+mod data;
+
 /// A [`BinaryArray`] is Arrow's semantically equivalent of an immutable `Vec<Option<Vec<u8>>>`.
 /// It implements [`Array`].
 ///
diff --git a/src/array/boolean/data.rs b/src/array/boolean/data.rs
new file mode 100644
index 0000000000..ed430fe2c5
--- /dev/null
+++ b/src/array/boolean/data.rs
@@ -0,0 +1,35 @@
+use crate::array::{Arrow2Arrow, BooleanArray};
+use crate::bitmap::Bitmap;
+use crate::datatypes::DataType;
+use arrow_buffer::{BooleanBuffer, NullBuffer};
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl Arrow2Arrow for BooleanArray {
+    fn to_data(&self) -> ArrayData {
+        let buffer = NullBuffer::from(self.values.clone());
+
+        let builder = ArrayDataBuilder::new(arrow_schema::DataType::Boolean)
+            .len(buffer.len())
+            .offset(buffer.offset())
+            .buffers(vec![buffer.into_inner().into_inner()])
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()));
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        assert_eq!(data.data_type(), &arrow_schema::DataType::Boolean);
+
+        let buffers = data.buffers();
+        let buffer = BooleanBuffer::new(buffers[0].clone(), data.offset(), data.len());
+        // Use NullBuffer to compute set count
+        let values = Bitmap::from_null_buffer(NullBuffer::new(buffer));
+
+        Self {
+            data_type: DataType::Boolean,
+            values,
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/boolean/mod.rs b/src/array/boolean/mod.rs
index f470ea6fa1..78555572bb 100644
--- a/src/array/boolean/mod.rs
+++ b/src/array/boolean/mod.rs
@@ -11,6 +11,8 @@ use either::Either;
 
 use super::Array;
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod from;
diff --git a/src/array/dictionary/data.rs b/src/array/dictionary/data.rs
new file mode 100644
index 0000000000..286e02dc91
--- /dev/null
+++ b/src/array/dictionary/data.rs
@@ -0,0 +1,48 @@
+use crate::array::{
+    from_data, to_data, Arrow2Arrow, DictionaryArray, DictionaryKey, PrimitiveArray,
+};
+use crate::datatypes::{DataType, PhysicalType};
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl<K: DictionaryKey> Arrow2Arrow for DictionaryArray<K> {
+    fn to_data(&self) -> ArrayData {
+        let keys = self.keys.to_data();
+        let builder = keys
+            .into_builder()
+            .data_type(self.data_type.clone().into())
+            .child_data(vec![to_data(self.values.as_ref())]);
+
+        // Safety: Dictionary is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let key = match data.data_type() {
+            arrow_schema::DataType::Dictionary(k, _) => k.as_ref(),
+            d => panic!("unsupported dictionary type {d}"),
+        };
+
+        let data_type = DataType::from(data.data_type().clone());
+        assert_eq!(
+            data_type.to_physical_type(),
+            PhysicalType::Dictionary(K::KEY_TYPE)
+        );
+
+        let key_builder = ArrayDataBuilder::new(key.clone())
+            .buffers(vec![data.buffers()[0].clone()])
+            .offset(data.offset())
+            .len(data.len())
+            .nulls(data.nulls().cloned());
+
+        // Safety: Dictionary is valid
+        let key_data = unsafe { key_builder.build_unchecked() };
+        let keys = PrimitiveArray::from_data(&key_data);
+        let values = from_data(&data.child_data()[0]);
+
+        Self {
+            data_type,
+            keys,
+            values,
+        }
+    }
+}
diff --git a/src/array/dictionary/mod.rs b/src/array/dictionary/mod.rs
index bf11567832..f7d4a0f43d 100644
--- a/src/array/dictionary/mod.rs
+++ b/src/array/dictionary/mod.rs
@@ -12,6 +12,8 @@ use crate::{
     types::NativeType,
 };
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod iterator;
diff --git a/src/array/fixed_size_binary/data.rs b/src/array/fixed_size_binary/data.rs
new file mode 100644
index 0000000000..bd000610c1
--- /dev/null
+++ b/src/array/fixed_size_binary/data.rs
@@ -0,0 +1,36 @@
+use crate::array::{Arrow2Arrow, FixedSizeBinaryArray};
+use crate::bitmap::Bitmap;
+use crate::buffer::Buffer;
+use crate::datatypes::DataType;
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl Arrow2Arrow for FixedSizeBinaryArray {
+    fn to_data(&self) -> ArrayData {
+        let data_type = self.data_type.clone().into();
+        let builder = ArrayDataBuilder::new(data_type)
+            .len(self.len())
+            .buffers(vec![self.values.clone().into()])
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()));
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type: DataType = data.data_type().clone().into();
+        let size = match data_type {
+            DataType::FixedSizeBinary(size) => size,
+            _ => unreachable!("must be FixedSizeBinary"),
+        };
+
+        let mut values: Buffer<u8> = data.buffers()[0].clone().into();
+        values.slice(data.offset() * size, data.len() * size);
+
+        Self {
+            size,
+            data_type,
+            values,
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/fixed_size_binary/mod.rs b/src/array/fixed_size_binary/mod.rs
index f1aeee53a9..34242d9ad6 100644
--- a/src/array/fixed_size_binary/mod.rs
+++ b/src/array/fixed_size_binary/mod.rs
@@ -2,6 +2,8 @@ use crate::{bitmap::Bitmap, buffer::Buffer, datatypes::DataType, error::Error};
 
 use super::Array;
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod iterator;
diff --git a/src/array/fixed_size_list/data.rs b/src/array/fixed_size_list/data.rs
new file mode 100644
index 0000000000..3a2c37d3af
--- /dev/null
+++ b/src/array/fixed_size_list/data.rs
@@ -0,0 +1,35 @@
+use crate::array::{from_data, to_data, Arrow2Arrow, FixedSizeListArray};
+use crate::bitmap::Bitmap;
+use crate::datatypes::DataType;
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl Arrow2Arrow for FixedSizeListArray {
+    fn to_data(&self) -> ArrayData {
+        let data_type = self.data_type.clone().into();
+        let builder = ArrayDataBuilder::new(data_type)
+            .len(self.len())
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()))
+            .child_data(vec![to_data(self.values.as_ref())]);
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type: DataType = data.data_type().clone().into();
+        let size = match data_type {
+            DataType::FixedSizeList(_, size) => size,
+            _ => unreachable!("must be FixedSizeList type"),
+        };
+
+        let mut values = from_data(&data.child_data()[0]);
+        values.slice(data.offset() * size, data.len() * size);
+
+        Self {
+            size,
+            data_type,
+            values,
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/fixed_size_list/mod.rs b/src/array/fixed_size_list/mod.rs
index 821d219e69..0d335167b2 100644
--- a/src/array/fixed_size_list/mod.rs
+++ b/src/array/fixed_size_list/mod.rs
@@ -6,6 +6,8 @@ use crate::{
 
 use super::{new_empty_array, new_null_array, Array};
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod iterator;
diff --git a/src/array/list/data.rs b/src/array/list/data.rs
new file mode 100644
index 0000000000..24f322c682
--- /dev/null
+++ b/src/array/list/data.rs
@@ -0,0 +1,37 @@
+use crate::array::{from_data, to_data, Arrow2Arrow, ListArray};
+use crate::bitmap::Bitmap;
+use crate::offset::{Offset, OffsetsBuffer};
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl<O: Offset> Arrow2Arrow for ListArray<O> {
+    fn to_data(&self) -> ArrayData {
+        let data_type = self.data_type.clone().into();
+
+        let builder = ArrayDataBuilder::new(data_type)
+            .len(self.len())
+            .buffers(vec![self.offsets.clone().into_inner().into()])
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()))
+            .child_data(vec![to_data(self.values.as_ref())]);
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type = data.data_type().clone().into();
+        if data.is_empty() {
+            // Handle empty offsets
+            return Self::new_empty(data_type);
+        }
+
+        let mut offsets = unsafe { OffsetsBuffer::new_unchecked(data.buffers()[0].clone().into()) };
+        offsets.slice(data.offset(), data.len() + 1);
+
+        Self {
+            data_type,
+            offsets,
+            values: from_data(&data.child_data()[0]),
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/list/mod.rs b/src/array/list/mod.rs
index 6d6fe22eb8..b7eda9b4d5 100644
--- a/src/array/list/mod.rs
+++ b/src/array/list/mod.rs
@@ -7,6 +7,8 @@ use crate::{
 
 use super::{new_empty_array, specification::try_check_offsets_bounds, Array};
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod iterator;
diff --git a/src/array/map/data.rs b/src/array/map/data.rs
new file mode 100644
index 0000000000..3a394644d7
--- /dev/null
+++ b/src/array/map/data.rs
@@ -0,0 +1,37 @@
+use crate::array::{from_data, to_data, Arrow2Arrow, MapArray};
+use crate::bitmap::Bitmap;
+use crate::offset::OffsetsBuffer;
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl Arrow2Arrow for MapArray {
+    fn to_data(&self) -> ArrayData {
+        let data_type = self.data_type.clone().into();
+
+        let builder = ArrayDataBuilder::new(data_type)
+            .len(self.len())
+            .buffers(vec![self.offsets.clone().into_inner().into()])
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()))
+            .child_data(vec![to_data(self.field.as_ref())]);
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type = data.data_type().clone().into();
+        if data.is_empty() {
+            // Handle empty offsets
+            return Self::new_empty(data_type);
+        }
+
+        let mut offsets = unsafe { OffsetsBuffer::new_unchecked(data.buffers()[0].clone().into()) };
+        offsets.slice(data.offset(), data.len() + 1);
+
+        Self {
+            data_type: data.data_type().clone().into(),
+            offsets,
+            field: from_data(&data.child_data()[0]),
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/map/mod.rs b/src/array/map/mod.rs
index 96b9aacde2..952695297f 100644
--- a/src/array/map/mod.rs
+++ b/src/array/map/mod.rs
@@ -7,6 +7,8 @@ use crate::{
 
 use super::{new_empty_array, specification::try_check_offsets_bounds, Array};
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod iterator;
diff --git a/src/array/mod.rs b/src/array/mod.rs
index 91c06f02bf..42b528fc27 100644
--- a/src/array/mod.rs
+++ b/src/array/mod.rs
@@ -389,6 +389,113 @@ pub fn new_null_array(data_type: DataType, length: usize) -> Box<dyn Array> {
     }
 }
 
+/// Trait providing bi-directional conversion between arrow2 [`Array`] and arrow-rs [`ArrayData`]
+///
+/// [`ArrayData`]: arrow_data::ArrayData
+#[cfg(feature = "arrow")]
+pub trait Arrow2Arrow: Array {
+    /// Convert this [`Array`] into [`ArrayData`]
+    fn to_data(&self) -> arrow_data::ArrayData;
+
+    /// Create this [`Array`] from [`ArrayData`]
+    fn from_data(data: &arrow_data::ArrayData) -> Self;
+}
+
+#[cfg(feature = "arrow")]
+macro_rules! to_data_dyn {
+    ($array:expr, $ty:ty) => {{
+        let f = |x: &$ty| x.to_data();
+        general_dyn!($array, $ty, f)
+    }};
+}
+
+#[cfg(feature = "arrow")]
+impl From<Box<dyn Array>> for arrow_array::ArrayRef {
+    fn from(value: Box<dyn Array>) -> Self {
+        value.as_ref().into()
+    }
+}
+
+#[cfg(feature = "arrow")]
+impl From<&dyn Array> for arrow_array::ArrayRef {
+    fn from(value: &dyn Array) -> Self {
+        arrow_array::make_array(to_data(value))
+    }
+}
+
+#[cfg(feature = "arrow")]
+impl From<arrow_array::ArrayRef> for Box<dyn Array> {
+    fn from(value: arrow_array::ArrayRef) -> Self {
+        value.as_ref().into()
+    }
+}
+
+#[cfg(feature = "arrow")]
+impl From<&dyn arrow_array::Array> for Box<dyn Array> {
+    fn from(value: &dyn arrow_array::Array) -> Self {
+        from_data(&value.to_data())
+    }
+}
+
+/// Convert an arrow2 [`Array`] to [`arrow_data::ArrayData`]
+#[cfg(feature = "arrow")]
+pub fn to_data(array: &dyn Array) -> arrow_data::ArrayData {
+    use crate::datatypes::PhysicalType::*;
+    match array.data_type().to_physical_type() {
+        Null => to_data_dyn!(array, NullArray),
+        Boolean => to_data_dyn!(array, BooleanArray),
+        Primitive(primitive) => with_match_primitive_type!(primitive, |$T| {
+            to_data_dyn!(array, PrimitiveArray<$T>)
+        }),
+        Binary => to_data_dyn!(array, BinaryArray<i32>),
+        LargeBinary => to_data_dyn!(array, BinaryArray<i64>),
+        FixedSizeBinary => to_data_dyn!(array, FixedSizeBinaryArray),
+        Utf8 => to_data_dyn!(array, Utf8Array::<i32>),
+        LargeUtf8 => to_data_dyn!(array, Utf8Array::<i64>),
+        List => to_data_dyn!(array, ListArray::<i32>),
+        LargeList => to_data_dyn!(array, ListArray::<i64>),
+        FixedSizeList => to_data_dyn!(array, FixedSizeListArray),
+        Struct => to_data_dyn!(array, StructArray),
+        Union => to_data_dyn!(array, UnionArray),
+        Dictionary(key_type) => {
+            match_integer_type!(key_type, |$T| {
+                to_data_dyn!(array, DictionaryArray::<$T>)
+            })
+        }
+        Map => to_data_dyn!(array, MapArray),
+    }
+}
+
+/// Convert an [`arrow_data::ArrayData`] to arrow2 [`Array`]
+#[cfg(feature = "arrow")]
+pub fn from_data(data: &arrow_data::ArrayData) -> Box<dyn Array> {
+    use crate::datatypes::PhysicalType::*;
+    let data_type: DataType = data.data_type().clone().into();
+    match data_type.to_physical_type() {
+        Null => Box::new(NullArray::from_data(data)),
+        Boolean => Box::new(BooleanArray::from_data(data)),
+        Primitive(primitive) => with_match_primitive_type!(primitive, |$T| {
+            Box::new(PrimitiveArray::<$T>::from_data(data))
+        }),
+        Binary => Box::new(BinaryArray::<i32>::from_data(data)),
+        LargeBinary => Box::new(BinaryArray::<i64>::from_data(data)),
+        FixedSizeBinary => Box::new(FixedSizeBinaryArray::from_data(data)),
+        Utf8 => Box::new(Utf8Array::<i32>::from_data(data)),
+        LargeUtf8 => Box::new(Utf8Array::<i64>::from_data(data)),
+        List => Box::new(ListArray::<i32>::from_data(data)),
+        LargeList => Box::new(ListArray::<i64>::from_data(data)),
+        FixedSizeList => Box::new(FixedSizeListArray::from_data(data)),
+        Struct => Box::new(StructArray::from_data(data)),
+        Union => Box::new(UnionArray::from_data(data)),
+        Dictionary(key_type) => {
+            match_integer_type!(key_type, |$T| {
+                Box::new(DictionaryArray::<$T>::from_data(data))
+            })
+        }
+        Map => Box::new(MapArray::from_data(data)),
+    }
+}
+
 macro_rules! clone_dyn {
     ($array:expr, $ty:ty) => {{
         let f = |x: &$ty| Box::new(x.clone());
diff --git a/src/array/null.rs b/src/array/null.rs
index 0cf8e600e4..5a1471efcf 100644
--- a/src/array/null.rs
+++ b/src/array/null.rs
@@ -116,3 +116,23 @@ impl<A: ffi::ArrowArrayRef> FromFfi<A> for NullArray {
         Self::try_new(data_type, array.array().len())
     }
 }
+
+#[cfg(feature = "arrow")]
+mod arrow {
+    use super::*;
+    use arrow_data::{ArrayData, ArrayDataBuilder};
+    impl NullArray {
+        /// Convert this array into [`arrow_data::ArrayData`]
+        pub fn to_data(&self) -> ArrayData {
+            let builder = ArrayDataBuilder::new(arrow_schema::DataType::Null).len(self.len());
+
+            // Safety: safe by construction
+            unsafe { builder.build_unchecked() }
+        }
+
+        /// Create this array from [`ArrayData`]
+        pub fn from_data(data: &ArrayData) -> Self {
+            Self::new(DataType::Null, data.len())
+        }
+    }
+}
diff --git a/src/array/primitive/data.rs b/src/array/primitive/data.rs
new file mode 100644
index 0000000000..33ba9d97ea
--- /dev/null
+++ b/src/array/primitive/data.rs
@@ -0,0 +1,32 @@
+use crate::array::{Arrow2Arrow, PrimitiveArray};
+use crate::bitmap::Bitmap;
+use crate::buffer::Buffer;
+use crate::types::NativeType;
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl<T: NativeType> Arrow2Arrow for PrimitiveArray<T> {
+    fn to_data(&self) -> ArrayData {
+        let data_type = self.data_type.clone().into();
+
+        let builder = ArrayDataBuilder::new(data_type)
+            .len(self.len())
+            .buffers(vec![self.values.clone().into()])
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()));
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type = data.data_type().clone().into();
+
+        let mut values: Buffer<T> = data.buffers()[0].clone().into();
+        values.slice(data.offset(), data.len());
+
+        Self {
+            data_type,
+            values,
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/primitive/mod.rs b/src/array/primitive/mod.rs
index f9942fede3..9e4ae03881 100644
--- a/src/array/primitive/mod.rs
+++ b/src/array/primitive/mod.rs
@@ -13,6 +13,8 @@ use crate::{
 use super::Array;
 use either::Either;
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod from_natural;
diff --git a/src/array/struct_/data.rs b/src/array/struct_/data.rs
new file mode 100644
index 0000000000..9ce016ebb1
--- /dev/null
+++ b/src/array/struct_/data.rs
@@ -0,0 +1,27 @@
+use crate::array::{from_data, to_data, Arrow2Arrow, StructArray};
+use crate::bitmap::Bitmap;
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl Arrow2Arrow for StructArray {
+    fn to_data(&self) -> ArrayData {
+        let data_type = self.data_type.clone().into();
+
+        let builder = ArrayDataBuilder::new(data_type)
+            .len(self.len())
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()))
+            .child_data(self.values.iter().map(|x| to_data(x.as_ref())).collect());
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type = data.data_type().clone().into();
+
+        Self {
+            data_type,
+            values: data.child_data().iter().map(from_data).collect(),
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/struct_/mod.rs b/src/array/struct_/mod.rs
index 04548504f2..767ba8242f 100644
--- a/src/array/struct_/mod.rs
+++ b/src/array/struct_/mod.rs
@@ -6,6 +6,8 @@ use crate::{
 
 use super::{new_empty_array, new_null_array, Array};
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod iterator;
diff --git a/src/array/union/data.rs b/src/array/union/data.rs
new file mode 100644
index 0000000000..0c8887e4cc
--- /dev/null
+++ b/src/array/union/data.rs
@@ -0,0 +1,69 @@
+use crate::array::{from_data, to_data, Arrow2Arrow, UnionArray};
+use crate::buffer::Buffer;
+use crate::datatypes::DataType;
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl Arrow2Arrow for UnionArray {
+    fn to_data(&self) -> ArrayData {
+        let data_type = arrow_schema::DataType::from(self.data_type.clone());
+        let len = self.len();
+
+        let builder = match self.offsets.clone() {
+            Some(offsets) => ArrayDataBuilder::new(data_type)
+                .len(len)
+                .buffers(vec![self.types.clone().into(), offsets.into()])
+                .child_data(self.fields.iter().map(|x| to_data(x.as_ref())).collect()),
+            None => ArrayDataBuilder::new(data_type)
+                .len(len)
+                .buffers(vec![self.types.clone().into()])
+                .child_data(
+                    self.fields
+                        .iter()
+                        .map(|x| to_data(x.as_ref()).slice(self.offset, len))
+                        .collect(),
+                ),
+        };
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type: DataType = data.data_type().clone().into();
+
+        let fields = data.child_data().iter().map(from_data).collect();
+        let buffers = data.buffers();
+        let mut types: Buffer<i8> = buffers[0].clone().into();
+        types.slice(data.offset(), data.len());
+        let offsets = match buffers.len() == 2 {
+            true => {
+                let mut offsets: Buffer<i32> = buffers[1].clone().into();
+                offsets.slice(data.offset(), data.len());
+                Some(offsets)
+            }
+            false => None,
+        };
+
+        // Map from type id to array index
+        let map = match &data_type {
+            DataType::Union(_, Some(ids), _) => {
+                let mut map = [0; 127];
+                for (pos, &id) in ids.iter().enumerate() {
+                    map[id as usize] = pos;
+                }
+                Some(map)
+            }
+            DataType::Union(_, None, _) => None,
+            _ => unreachable!("must be Union type"),
+        };
+
+        Self {
+            types,
+            map,
+            fields,
+            offsets,
+            data_type,
+            offset: data.offset(),
+        }
+    }
+}
diff --git a/src/array/union/mod.rs b/src/array/union/mod.rs
index c3bf98a3a9..e3e664916f 100644
--- a/src/array/union/mod.rs
+++ b/src/array/union/mod.rs
@@ -8,6 +8,8 @@ use crate::{
 
 use super::{new_empty_array, new_null_array, Array};
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod iterator;
diff --git a/src/array/utf8/data.rs b/src/array/utf8/data.rs
new file mode 100644
index 0000000000..94d7689840
--- /dev/null
+++ b/src/array/utf8/data.rs
@@ -0,0 +1,41 @@
+use crate::array::{Arrow2Arrow, Utf8Array};
+use crate::bitmap::Bitmap;
+use crate::offset::{Offset, OffsetsBuffer};
+use arrow_data::{ArrayData, ArrayDataBuilder};
+
+impl<O: Offset> Arrow2Arrow for Utf8Array<O> {
+    fn to_data(&self) -> ArrayData {
+        let data_type = self.data_type().clone().into();
+        let builder = ArrayDataBuilder::new(data_type)
+            .len(self.offsets().len_proxy())
+            .buffers(vec![
+                self.offsets.clone().into_inner().into(),
+                self.values.clone().into(),
+            ])
+            .nulls(self.validity.as_ref().map(|b| b.clone().into()));
+
+        // Safety: Array is valid
+        unsafe { builder.build_unchecked() }
+    }
+
+    fn from_data(data: &ArrayData) -> Self {
+        let data_type = data.data_type().clone().into();
+        if data.is_empty() {
+            // Handle empty offsets
+            return Self::new_empty(data_type);
+        }
+
+        let buffers = data.buffers();
+
+        // Safety: ArrayData is valid
+        let mut offsets = unsafe { OffsetsBuffer::new_unchecked(buffers[0].clone().into()) };
+        offsets.slice(data.offset(), data.len() + 1);
+
+        Self {
+            data_type,
+            offsets,
+            values: buffers[1].clone().into(),
+            validity: data.nulls().map(|n| Bitmap::from_null_buffer(n.clone())),
+        }
+    }
+}
diff --git a/src/array/utf8/mod.rs b/src/array/utf8/mod.rs
index c5a69576c5..7392ccdd04 100644
--- a/src/array/utf8/mod.rs
+++ b/src/array/utf8/mod.rs
@@ -17,6 +17,8 @@ use super::{
     Array, GenericBinaryArray,
 };
 
+#[cfg(feature = "arrow")]
+mod data;
 mod ffi;
 pub(super) mod fmt;
 mod from;
diff --git a/src/buffer/immutable.rs b/src/buffer/immutable.rs
index cbef5cb6d7..98a31cb153 100644
--- a/src/buffer/immutable.rs
+++ b/src/buffer/immutable.rs
@@ -287,6 +287,9 @@ impl<T: crate::types::NativeType> From<arrow_buffer::Buffer> for Buffer<T> {
 #[cfg(feature = "arrow")]
 impl<T: crate::types::NativeType> From<Buffer<T>> for arrow_buffer::Buffer {
     fn from(value: Buffer<T>) -> Self {
-        crate::buffer::to_buffer(value.data)
+        crate::buffer::to_buffer(value.data).slice_with_length(
+            value.offset * std::mem::size_of::<T>(),
+            value.length * std::mem::size_of::<T>(),
+        )
     }
 }
diff --git a/src/compute/boolean.rs b/src/compute/boolean.rs
index 8a17eeaba3..b8200ef5a9 100644
--- a/src/compute/boolean.rs
+++ b/src/compute/boolean.rs
@@ -244,7 +244,9 @@ pub fn any(array: &BooleanArray) -> bool {
 
 /// Check if all of the values in the array are `true`
 pub fn all(array: &BooleanArray) -> bool {
-    if array.is_empty() || array.null_count() > 0 {
+    if array.is_empty() {
+        true
+    } else if array.null_count() > 0 {
         false
     } else {
         let vals = array.values();
diff --git a/src/compute/boolean_kleene.rs b/src/compute/boolean_kleene.rs
index 48d90433ee..fc6b717543 100644
--- a/src/compute/boolean_kleene.rs
+++ b/src/compute/boolean_kleene.rs
@@ -248,7 +248,9 @@ pub fn any(array: &BooleanArray) -> bool {
 
 /// Returns whether all values in the array are `true`
 pub fn all(array: &BooleanArray) -> bool {
-    if array.is_empty() || array.null_count() > 0 {
+    if array.is_empty() {
+        true
+    } else if array.null_count() > 0 {
         false
     } else {
         let vals = array.values();
diff --git a/src/offset.rs b/src/offset.rs
index 744fb31966..80b45d6680 100644
--- a/src/offset.rs
+++ b/src/offset.rs
@@ -442,9 +442,11 @@ impl<O: Offset> OffsetsBuffer<O> {
 
     /// Slices this [`OffsetsBuffer`].
     /// # Panics
-    /// Panics iff `offset + length` is larger than `len`.
+    /// Panics if `offset + length` is larger than `len`
+    /// or `length == 0`.
     #[inline]
     pub fn slice(&mut self, offset: usize, length: usize) {
+        assert!(length > 0);
         self.0.slice(offset, length);
     }
 


Test patch:
diff --git a/tests/it/arrow.rs b/tests/it/arrow.rs
new file mode 100644
index 0000000000..fb02fafbbc
--- /dev/null
+++ b/tests/it/arrow.rs
@@ -0,0 +1,366 @@
+use arrow2::array::*;
+use arrow2::bitmap::Bitmap;
+use arrow2::datatypes::{DataType, Field, IntegerType, TimeUnit, UnionMode};
+use arrow2::offset::Offsets;
+use arrow_array::ArrayRef;
+use arrow_data::ArrayDataBuilder;
+use proptest::num::i32;
+
+fn test_arrow2_roundtrip(array: &dyn arrow_array::Array) {
+    let arrow2 = Box::<dyn Array>::from(array);
+    assert_eq!(arrow2.len(), array.len());
+
+    let back = ArrayRef::from(arrow2);
+    assert_eq!(back.len(), array.len());
+
+    match array.data_type() {
+        d @ arrow_schema::DataType::Union(_, _, arrow_schema::UnionMode::Sparse) => {
+            // Temporary workaround https://github.com/apache/arrow-rs/issues/4044
+            let data = array.to_data();
+            let type_ids = data.buffers()[0].slice_with_length(data.offset(), data.len());
+            let child_data = data
+                .child_data()
+                .iter()
+                .map(|x| x.slice(data.offset(), data.len()))
+                .collect();
+
+            let data = ArrayDataBuilder::new(d.clone())
+                .len(data.len())
+                .buffers(vec![type_ids])
+                .child_data(child_data)
+                .build()
+                .unwrap();
+
+            assert_eq!(back.to_data(), data);
+        }
+        _ => assert_eq!(array, back.as_ref()),
+    }
+    assert_eq!(array.data_type(), back.data_type());
+}
+
+fn test_arrow_roundtrip(array: &dyn Array) {
+    let arrow = ArrayRef::from(array);
+    assert_eq!(arrow.len(), array.len());
+
+    let back = Box::<dyn Array>::from(arrow);
+    assert_eq!(back.len(), array.len());
+
+    assert_eq!(array, back.as_ref());
+    assert_eq!(array.data_type(), back.data_type());
+}
+
+fn test_conversion(array: &dyn Array) {
+    test_arrow_roundtrip(array);
+    let to_arrow = ArrayRef::from(array);
+    test_arrow2_roundtrip(to_arrow.as_ref());
+
+    if !array.is_empty() {
+        let sliced = array.sliced(1, array.len() - 1);
+        test_arrow_roundtrip(sliced.as_ref());
+
+        let sliced = to_arrow.slice(1, array.len() - 1);
+        test_arrow2_roundtrip(sliced.as_ref());
+
+        let sliced = array.sliced(0, array.len() - 1);
+        test_arrow_roundtrip(sliced.as_ref());
+
+        let sliced = to_arrow.slice(0, array.len() - 1);
+        test_arrow2_roundtrip(sliced.as_ref());
+    }
+
+    if array.len() > 2 {
+        let sliced = array.sliced(1, array.len() - 2);
+        test_arrow_roundtrip(sliced.as_ref());
+
+        let sliced = to_arrow.slice(1, array.len() - 2);
+        test_arrow2_roundtrip(sliced.as_ref());
+    }
+}
+
+#[test]
+fn test_null() {
+    let data_type = DataType::Null;
+    let array = NullArray::new(data_type, 7);
+    test_conversion(&array);
+}
+
+#[test]
+fn test_primitive() {
+    let data_type = DataType::Int32;
+    let array = PrimitiveArray::new(data_type, vec![1, 2, 3].into(), None);
+    test_conversion(&array);
+
+    let data_type = DataType::Timestamp(TimeUnit::Second, Some("UTC".into()));
+    let nulls = Bitmap::from_iter([true, true, false]);
+    let array = PrimitiveArray::new(data_type, vec![1_i64, 24, 0].into(), Some(nulls));
+    test_conversion(&array);
+}
+
+#[test]
+fn test_boolean() {
+    let data_type = DataType::Boolean;
+    let values = [false, false, true, true, true].into_iter().collect();
+    let validity = [false, true, true, false, false].into_iter().collect();
+    let array = BooleanArray::new(data_type, values, Some(validity));
+    test_conversion(&array);
+}
+
+#[test]
+fn test_utf8() {
+    let array = Utf8Array::<i32>::from_iter([Some("asd\0"), None, Some("45\0848"), Some("")]);
+    test_conversion(&array);
+
+    let array = Utf8Array::<i64>::from_iter([Some("asd"), None, Some("45\n848"), Some("")]);
+    test_conversion(&array);
+
+    let array = Utf8Array::<i32>::new_empty(DataType::Utf8);
+    test_conversion(&array);
+}
+
+#[test]
+fn test_binary() {
+    let array = BinaryArray::<i32>::from_iter([Some("s".as_bytes()), Some(b"sd\xFFfk\x23"), None]);
+    test_conversion(&array);
+
+    let array = BinaryArray::<i64>::from_iter([Some("45848".as_bytes()), Some(b"\x03\xFF"), None]);
+    test_conversion(&array);
+
+    let array = BinaryArray::<i32>::new_empty(DataType::Binary);
+    test_conversion(&array);
+}
+
+/// Returns a 3 element struct array
+fn make_struct() -> StructArray {
+    let a1 = BinaryArray::<i32>::from_iter([Some("s".as_bytes()), Some(b"sd\xFFfk\x23"), None]);
+    let a2 = BinaryArray::<i64>::from_iter([Some("45848".as_bytes()), Some(b"\x03\xFF"), None]);
+
+    let data_type = DataType::Timestamp(TimeUnit::Millisecond, Some("UTC".into()));
+    let nulls = Bitmap::from_iter([true, true, false]);
+    let a3 = PrimitiveArray::new(data_type, vec![1_i64, 24, 0].into(), Some(nulls));
+
+    let nulls = [true, true, false].into_iter().collect();
+    StructArray::new(
+        DataType::Struct(vec![
+            Field::new("a1", a1.data_type().clone(), true),
+            Field::new("a2", a2.data_type().clone(), true),
+            Field::new("a3", a3.data_type().clone(), true),
+        ]),
+        vec![Box::new(a1), Box::new(a2), Box::new(a3)],
+        Some(nulls),
+    )
+}
+
+#[test]
+fn test_struct() {
+    let array = make_struct();
+    test_conversion(&array);
+}
+
+#[test]
+fn test_list() {
+    let values = Utf8Array::<i32>::from_iter([
+        Some("asd\0"),
+        None,
+        Some("45\0848"),
+        Some(""),
+        Some("335"),
+        Some("test"),
+    ]);
+
+    let validity = [true, true, false, false, true].into_iter().collect();
+    let offsets = Offsets::try_from_iter(vec![0, 2, 2, 2, 0]).unwrap();
+    let data_type = DataType::List(Box::new(Field::new("element", DataType::Utf8, true)));
+    let list = ListArray::<i32>::new(
+        data_type.clone(),
+        offsets.into(),
+        Box::new(values.clone()),
+        Some(validity),
+    );
+
+    test_conversion(&list);
+
+    let list = ListArray::<i32>::new_empty(data_type);
+    test_conversion(&list);
+
+    let validity = [true, true, false, false, true].into_iter().collect();
+    let offsets = Offsets::try_from_iter(vec![0, 2, 2, 2, 0]).unwrap();
+    let data_type = DataType::LargeList(Box::new(Field::new("element", DataType::Utf8, true)));
+    let list = ListArray::<i64>::new(
+        data_type.clone(),
+        offsets.into(),
+        Box::new(values),
+        Some(validity),
+    );
+
+    test_conversion(&list);
+
+    let list = ListArray::<i64>::new_empty(data_type);
+    test_conversion(&list);
+}
+
+#[test]
+fn test_list_struct() {
+    let values = make_struct();
+    let validity = [true, true, false, true].into_iter().collect();
+    let offsets = Offsets::try_from_iter(vec![0, 1, 0, 2]).unwrap();
+    let list = ListArray::<i32>::new(
+        DataType::List(Box::new(Field::new(
+            "element",
+            values.data_type().clone(),
+            true,
+        ))),
+        offsets.into(),
+        Box::new(values),
+        Some(validity),
+    );
+
+    test_conversion(&list);
+}
+
+#[test]
+fn test_dictionary() {
+    let nulls = [true, false, true, true, true].into_iter().collect();
+    let keys = PrimitiveArray::new(DataType::Int16, vec![1_i16, 1, 0, 2, 2].into(), Some(nulls));
+    let values = make_struct();
+    let dictionary = DictionaryArray::try_new(
+        DataType::Dictionary(
+            IntegerType::Int16,
+            Box::new(values.data_type().clone()),
+            false,
+        ),
+        keys,
+        Box::new(values),
+    )
+    .unwrap();
+
+    test_conversion(&dictionary);
+}
+
+#[test]
+fn test_fixed_size_binary() {
+    let data = (0_u8..16).collect::<Vec<_>>();
+    let nulls = [false, false, true, true, true, false, false, true]
+        .into_iter()
+        .collect();
+
+    let array = FixedSizeBinaryArray::new(DataType::FixedSizeBinary(2), data.into(), Some(nulls));
+    test_conversion(&array);
+}
+
+#[test]
+fn test_fixed_size_list() {
+    let values = vec![1_i64, 2, 3, 4, 5, 6, 7, 8];
+    let nulls = [false, false, true, true, true, true, false, false]
+        .into_iter()
+        .collect();
+    let values = PrimitiveArray::new(DataType::Int64, values.into(), Some(nulls));
+
+    let nulls = [true, true, false, true].into_iter().collect();
+    let array = FixedSizeListArray::new(
+        DataType::FixedSizeList(Box::new(Field::new("element", DataType::Int64, true)), 2),
+        Box::new(values),
+        Some(nulls),
+    );
+
+    test_conversion(&array);
+}
+
+#[test]
+fn test_map() {
+    let keys = Utf8Array::<i32>::from_iter(
+        ["key1", "key2", "key3", "key1", "key2"]
+            .into_iter()
+            .map(Some),
+    );
+    let values = PrimitiveArray::<i32>::from_iter([Some(1), None, Some(3), Some(1), None]);
+    let fields = StructArray::new(
+        DataType::Struct(vec![
+            Field::new("keys", DataType::Utf8, false), // Cannot be nullable
+            Field::new("values", DataType::Int32, true),
+        ]),
+        vec![Box::new(keys), Box::new(values)],
+        None, // Cannot be nullable
+    );
+
+    let validity = [true, true, false, false].into_iter().collect();
+    let offsets = Offsets::try_from_iter(vec![0, 2, 0, 2]).unwrap();
+    let data_type = DataType::Map(
+        Box::new(Field::new("entries", fields.data_type().clone(), true)),
+        false,
+    );
+    let map = MapArray::new(
+        data_type.clone(),
+        offsets.into(),
+        Box::new(fields),
+        Some(validity),
+    );
+
+    test_conversion(&map);
+
+    let map = MapArray::new_empty(data_type);
+    test_conversion(&map);
+}
+
+#[test]
+fn test_dense_union() {
+    let fields = vec![
+        Field::new("a1", DataType::Int32, true),
+        Field::new("a2", DataType::Int64, true),
+    ];
+
+    let a1 = PrimitiveArray::from_iter([Some(2), None]);
+    let a2 = PrimitiveArray::from_iter([Some(2_i64), None, Some(3)]);
+
+    let types = vec![1, 0, 0, 1, 1];
+    let offsets = vec![0, 0, 1, 1, 2];
+    let union = UnionArray::new(
+        DataType::Union(fields.clone(), Some(vec![0, 1]), UnionMode::Dense),
+        types.into(),
+        vec![Box::new(a1.clone()), Box::new(a2.clone())],
+        Some(offsets.into()),
+    );
+
+    test_conversion(&union);
+
+    let types = vec![1, 4, 4, 1, 1];
+    let offsets = vec![0, 0, 1, 1, 2];
+    let union = UnionArray::new(
+        DataType::Union(fields, Some(vec![4, 1]), UnionMode::Dense),
+        types.into(),
+        vec![Box::new(a1), Box::new(a2)],
+        Some(offsets.into()),
+    );
+
+    test_conversion(&union);
+}
+
+#[test]
+fn test_sparse_union() {
+    let fields = vec![
+        Field::new("a1", DataType::Int32, true),
+        Field::new("a2", DataType::Int64, true),
+    ];
+
+    let a1 = PrimitiveArray::from_iter([None, Some(2), None, None, None]);
+    let a2 = PrimitiveArray::from_iter([Some(2_i64), None, None, None, Some(3)]);
+
+    let types = vec![1, 0, 0, 1, 1];
+    let union = UnionArray::new(
+        DataType::Union(fields.clone(), Some(vec![0, 1]), UnionMode::Sparse),
+        types.into(),
+        vec![Box::new(a1.clone()), Box::new(a2.clone())],
+        None,
+    );
+
+    test_conversion(&union);
+
+    let types = vec![1, 4, 4, 1, 1];
+    let union = UnionArray::new(
+        DataType::Union(fields, Some(vec![4, 1]), UnionMode::Sparse),
+        types.into(),
+        vec![Box::new(a1), Box::new(a2)],
+        None,
+    );
+
+    test_conversion(&union);
+}
diff --git a/tests/it/buffer/immutable.rs b/tests/it/buffer/immutable.rs
index 96980c7985..550b2ee170 100644
--- a/tests/it/buffer/immutable.rs
+++ b/tests/it/buffer/immutable.rs
@@ -107,3 +107,13 @@ fn from_arrow_misaligned() {
     let buffer = arrow_buffer::Buffer::from_vec(vec![1_i32, 2_i32, 3_i32]).slice(1);
     let _ = Buffer::<i32>::from(buffer);
 }
+
+#[test]
+#[cfg(feature = "arrow")]
+fn from_arrow_sliced() {
+    let buffer = arrow_buffer::Buffer::from_vec(vec![1_i32, 2_i32, 3_i32]);
+    let b = Buffer::<i32>::from(buffer);
+    let sliced = b.sliced(1, 2);
+    let back = arrow_buffer::Buffer::from(sliced);
+    assert_eq!(back.typed_data::<i32>(), &[2, 3]);
+}
diff --git a/tests/it/compute/boolean_kleene.rs b/tests/it/compute/boolean_kleene.rs
index 5c722b20bd..8dac6e63c4 100644
--- a/tests/it/compute/boolean_kleene.rs
+++ b/tests/it/compute/boolean_kleene.rs
@@ -214,3 +214,10 @@ fn array_or_none() {
 
     assert_eq!(result, expected);
 }
+
+#[test]
+fn array_empty() {
+    let array = BooleanArray::from(&[]);
+    assert!(!any(&array));
+    assert!(all(&array));
+}
diff --git a/tests/it/main.rs b/tests/it/main.rs
index 436969f47b..b67e19f6f3 100644
--- a/tests/it/main.rs
+++ b/tests/it/main.rs
@@ -2,6 +2,9 @@
 #![allow(clippy::unnecessary_lazy_evaluations)]
 
 mod array;
+#[cfg(feature = "arrow")]
+mod arrow;
+
 mod bitmap;
 mod buffer;
 mod ffi;