Skip to content

Removes IonElement trait, ElementRef impl #475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 7, 2023
Merged

Removes IonElement trait, ElementRef impl #475

merged 5 commits into from
Mar 7, 2023

Conversation

zslayton
Copy link
Contributor

@zslayton zslayton commented Mar 7, 2023

IonElement provides a trait abstraction over two flavors of Element API:

  1. The 'borrowed' API, where each ElementRef has a lifetime tied to the lifetime of the source data.

  2. The 'owned' API, where each Element is self-contained, with no connection to the source data.

While the borrowed API was intended to be lighter weight, the current implementation materializes nearly the same amount of data as the owned API. Collections (list, sexp, struct) are heap-allocated. Of the scalar types, only strings and lobs avoid any allocation in the borrowed API.

The IonElement trait was designed before GATs were stabilized and had some rough edges caused by the inherent difficulty in abstracting over lifetime constraints in traits in the absence of GATs.

In practice, users that need an Element API are exclusively using the owned variant.

In light of these observations and in anticipation of the upcoming v1.0 release, this PR removes both the IonElement trait and the borrowed implementation, leaving only the owned Element API. These constructs can be reintroduced in the future if and when they are needed.

Follow-on pull requests will:

  • Offer builder APIs to construct Struct, List, and SExpression more ergonomically.
  • Merge ElementReader's functionality into IonReader.
  • Merge ElementWriter's functionality into IonWriter.
  • Restore the documentation and examples to reflect the new APIs.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

`IonElement` provides a trait abstraction over two flavors of
`Element` API:

1. The 'borrowed' API, where each `ElementRef` has a lifetime
   tied to the lifetime of the source data.

2. The 'owned' API, where each `Element` is self-contained, with
   no connection to the source data.

While the borrowed API should be lighter weight, the current
implementation performs materializes nearly the same amount of data
as the owned API. Collections (`list`, `sexp`, `struct`) are
heap-allocated; only `string`s and `lob`s avoid allocation.

The `IonElement` trait was designed before GATs were stabilized
and had some rough edges caused by the inherent difficulty in
abstracting over lifetime constraints in traits in the absence of GATs.

In practice, users that need an `Element` API are exclusively using
the `owned` variant.

In light of these observations and in anticipation of the upcoming v1.0
release, this PR removes both the `IonElement` trait and the `borrowed`
implementation, leaving only the owned `Element` API. These constructs
can be reintroduced in the future if and when they are needed.
@zslayton zslayton requested review from nirosys and desaikd March 7, 2023 15:10
@codecov
Copy link

codecov bot commented Mar 7, 2023

Codecov Report

Patch coverage: 89.15% and project coverage change: -0.26 ⚠️

Comparison is base (2a8e839) 90.40% compared to head (3ef873c) 90.15%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #475      +/-   ##
==========================================
- Coverage   90.40%   90.15%   -0.26%     
==========================================
  Files          77       76       -1     
  Lines       14179    13548     -631     
==========================================
- Hits        12819    12214     -605     
+ Misses       1360     1334      -26     
Impacted Files Coverage Δ
src/types/integer.rs 89.18% <ø> (+0.33%) ⬆️
src/value/iterators.rs 80.00% <ø> (ø)
src/value/owned.rs 83.02% <60.29%> (-11.83%) ⬇️
src/value/element_stream_reader.rs 86.58% <85.71%> (-0.05%) ⬇️
src/value/native_writer.rs 91.22% <88.88%> (ø)
src/value/mod.rs 92.26% <98.78%> (+3.55%) ⬆️
ion-hash/src/element_hasher.rs 98.00% <100.00%> (ø)
ion-hash/src/lib.rs 100.00% <100.00%> (ø)
ion-hash/src/representation.rs 99.00% <100.00%> (ø)
ion-hash/src/type_qualifier.rs 100.00% <100.00%> (ø)
... and 15 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

Copy link
Contributor Author

@zslayton zslayton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ PR tour

While most of the changes in the diff are pretty mechanical (removing generics in favor of Element), there are a few other changes that I'll point out below.

match integer_val {
Integer::I64(i64_int) => i64_int.into(),
Integer::BigInt(big_int) => big_int.into(),
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ We already had From<i64> (e.g. 16.into()) and From<BigInt>, adding From<Integer> allows simple conversions in places where we've already constructed an Integer enum.

Value::String(string_val.to_owned()).into()
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Allows let s: Element = "hello".into();

type AnnotationsIterator<'a> = SymbolsIterator<'a>;

fn ion_type(&self) -> IonType {
impl Element {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ For now I've converted the functionality that was associated with the IonElement trait into concrete methods on the Element type. I expect to refactor these in a follow-on PR.

match &self.value {
Value::Integer(i) => Some(i),
_ => None,
}
}

fn as_f64(&self) -> Option<f64> {
pub fn as_float(&self) -> Option<f64> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ I renamed this to reflect the Ion type (float) rather than the Rust type (f64) as we do in the other methods.

match &self.value {
Value::Timestamp(t) => Some(t),
_ => None,
}
}

fn as_str(&self) -> Option<&str> {
pub fn as_text(&self) -> Option<&str> {
match &self.value {
Value::String(text) => Some(text),
Value::Symbol(sym) => sym.text(),
_ => None,
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Rather than have a single as_string() method that returns text for either String or Symbol elements, we now have as_string() for String elements, as_symbol() for Symbol elements, and as_text() for the old behavior that doesn't care which text type the Element is.

@@ -110,7 +95,7 @@ where
return Ok(());
}

self.update_escaping(&v.to_be_bytes());
self.update_escaping(v.to_be_bytes());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Incorporating a clippy suggestion.

@@ -154,7 +136,7 @@ where

fn write_repr_string(&mut self, value: Option<&str>) -> IonResult<()> {
match value {
Some(s) if s.len() > 0 => self.update_escaping(s.as_bytes()),
Some(s) if !s.is_empty() => self.update_escaping(s.as_bytes()),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Incorporating a clippy suggestion.

@@ -163,17 +145,14 @@ where

fn write_repr_blob(&mut self, value: Option<&[u8]>) -> IonResult<()> {
match value {
Some(bytes) if bytes.len() > 0 => self.update_escaping(bytes),
Some(bytes) if !bytes.is_empty() => self.update_escaping(bytes),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Incorporating a clippy suggestion.

match &self.value {
Value::Symbol(sym) => Some(sym),
_ => None,
}
}

fn as_bool(&self) -> Option<bool> {
pub fn as_boolean(&self) -> Option<bool> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Renaming to reflect the Ion type (boolean) rather than the Rust type (bool).

match &self.value {
Value::Boolean(b) => Some(*b),
_ => None,
}
}

fn as_bytes(&self) -> Option<&[u8]> {
pub fn as_lob(&self) -> Option<&[u8]> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ As with the string/symbol/text, I've broken this into clob/blob/lob.

Copy link
Contributor

@desaikd desaikd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 🚀
There are still some clippy warnings for element_stream_reader though.

@zslayton
Copy link
Contributor Author

zslayton commented Mar 7, 2023

There are still some clippy warnings for element_stream_reader though.

Those are related to #472.

@zslayton zslayton merged commit e3f7bd1 into main Mar 7, 2023
@zslayton zslayton deleted the element branch March 7, 2023 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants