Create Record type (#10103)

# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
   ```rust
   record! {
       "key1" => some_value,
       "key2" => Value::string("text", span),
       "key3" => Value::int(optional_int.unwrap_or(0), span),
       "key4" => Value::bool(config.setting, span),
   }
   ```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.

Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.

# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
This commit is contained in:
Ian Manske
2023-08-24 19:50:29 +00:00
committed by GitHub
parent 030e749fe7
commit 8da27a1a09
195 changed files with 4211 additions and 6245 deletions

View File

@@ -4,8 +4,7 @@ use std::str::FromStr;
use crate::ast::{CellPath, MatchPattern, PathMember};
use crate::engine::{Block, Closure};
use crate::ShellError;
use crate::{Range, Spanned, Value};
use crate::{Range, Record, ShellError, Spanned, Value};
use chrono::{DateTime, FixedOffset};
pub trait FromValue: Sized {
@@ -490,11 +489,10 @@ impl FromValue for Vec<Value> {
}
}
// A record
impl FromValue for (Vec<String>, Vec<Value>) {
impl FromValue for Record {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Record { cols, vals, .. } => Ok((cols.clone(), vals.clone())),
Value::Record { val, .. } => Ok(val.clone()),
v => Err(ShellError::CantConvert {
to_type: "Record".into(),
from_type: v.get_type().to_string(),

View File

@@ -1,4 +1,4 @@
use crate::{ShellError, Span, Value};
use crate::{Record, ShellError, Span, Value};
use std::fmt;
// Trait definition for a lazy record (where columns are evaluated on-demand)
@@ -15,20 +15,14 @@ pub trait LazyRecord<'a>: fmt::Debug + Send + Sync {
// Convert the lazy record into a regular Value::Record by collecting all its columns
fn collect(&'a self) -> Result<Value, ShellError> {
let mut cols = vec![];
let mut vals = vec![];
for column in self.column_names() {
cols.push(column.into());
let val = self.get_column_value(column)?;
vals.push(val);
}
Ok(Value::Record {
cols,
vals,
span: self.span(),
})
self.column_names()
.into_iter()
.map(|col| {
let val = self.get_column_value(col)?;
Ok((col.to_owned(), val))
})
.collect::<Result<Record, _>>()
.map(|record| Value::record(record, self.span()))
}
fn clone_value(&self, span: Span) -> Value;

View File

@@ -3,6 +3,7 @@ mod from;
mod from_value;
mod lazy_record;
mod range;
mod record;
mod stream;
mod unit;
@@ -18,19 +19,18 @@ use chrono_humanize::HumanTime;
pub use custom_value::CustomValue;
use fancy_regex::Regex;
pub use from_value::FromValue;
use indexmap::map::IndexMap;
pub use lazy_record::LazyRecord;
use nu_utils::get_system_locale;
use nu_utils::locale::get_system_locale_string;
use num_format::ToFormattedString;
pub use range::*;
pub use record::Record;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Write;
use std::{
borrow::Cow,
fmt::{Display, Formatter, Result as FmtResult},
iter,
path::PathBuf,
{cmp::Ordering, fmt::Debug},
};
@@ -75,8 +75,7 @@ pub enum Value {
span: Span,
},
Record {
cols: Vec<String>,
vals: Vec<Value>,
val: Record,
span: Span,
},
List {
@@ -148,9 +147,8 @@ impl Clone for Value {
val: val.clone(),
span: *span,
},
Value::Record { cols, vals, span } => Value::Record {
cols: cols.clone(),
vals: vals.clone(),
Value::Record { val, span } => Value::Record {
val: val.clone(),
span: *span,
},
Value::LazyRecord { val, span } => val.clone_value(*span),
@@ -367,9 +365,9 @@ impl Value {
}
}
pub fn as_record(&self) -> Result<(&[String], &[Value]), ShellError> {
pub fn as_record(&self) -> Result<&Record, ShellError> {
match self {
Value::Record { cols, vals, .. } => Ok((cols, vals)),
Value::Record { val, .. } => Ok(val),
x => Err(ShellError::CantConvert {
to_type: "record".into(),
from_type: x.get_type().to_string(),
@@ -547,12 +545,9 @@ impl Value {
Value::Date { .. } => Type::Date,
Value::Range { .. } => Type::Range,
Value::String { .. } => Type::String,
Value::Record { cols, vals, .. } => Type::Record(
cols.iter()
.zip(vals.iter())
.map(|(x, y)| (x.clone(), y.get_type()))
.collect(),
),
Value::Record { val, .. } => {
Type::Record(val.iter().map(|(x, y)| (x.clone(), y.get_type())).collect())
}
Value::List { vals, .. } => {
let mut ty = None;
for val in vals {
@@ -595,9 +590,8 @@ impl Value {
pub fn get_data_by_key(&self, name: &str) -> Option<Value> {
match self {
Value::Record { cols, vals, .. } => cols
Value::Record { val, .. } => val
.iter()
.zip(vals.iter())
.find(|(col, _)| col == &name)
.map(|(_, val)| val.clone()),
Value::List { vals, span } => {
@@ -676,10 +670,9 @@ impl Value {
.collect::<Vec<_>>()
.join(separator)
),
Value::Record { cols, vals, .. } => format!(
Value::Record { val, .. } => format!(
"{{{}}}",
cols.iter()
.zip(vals.iter())
val.iter()
.map(|(x, y)| format!("{}: {}", x, y.into_string(", ", config)))
.collect::<Vec<_>>()
.join(separator)
@@ -739,10 +732,10 @@ impl Value {
)
}
}
Value::Record { cols, .. } => format!(
Value::Record { val, .. } => format!(
"{{record {} field{}}}",
cols.len(),
if cols.len() == 1 { "" } else { "s" }
val.len(),
if val.len() == 1 { "" } else { "s" }
),
Value::LazyRecord { val, .. } => match val.collect() {
Ok(val) => val.into_abbreviated_string(config),
@@ -800,10 +793,9 @@ impl Value {
.collect::<Vec<_>>()
.join(separator)
),
Value::Record { cols, vals, .. } => format!(
Value::Record { val, .. } => format!(
"{{{}}}",
cols.iter()
.zip(vals.iter())
val.iter()
.map(|(x, y)| format!("{}: {}", x, y.into_string_parsable(", ", config)))
.collect::<Vec<_>>()
.join(separator)
@@ -838,10 +830,9 @@ impl Value {
.collect::<Vec<_>>()
.join(separator)
),
Value::Record { cols, vals, .. } => format!(
Value::Record { val, .. } => format!(
"{{{}}}",
cols.iter()
.zip(vals.iter())
val.iter()
.map(|(x, y)| format!("{}: {}", x, y.into_string(", ", config)))
.collect::<Vec<_>>()
.join(separator)
@@ -866,7 +857,7 @@ impl Value {
match self {
Value::String { val, .. } => val.is_empty(),
Value::List { vals, .. } => vals.is_empty(),
Value::Record { cols, .. } => cols.is_empty(),
Value::Record { val, .. } => val.is_empty(),
Value::Binary { val, .. } => val.is_empty(),
Value::Nothing { .. } => true,
_ => false,
@@ -981,12 +972,11 @@ impl Value {
span: origin_span,
optional,
} => match &mut current {
Value::Record { cols, vals, span } => {
let cols = cols.clone();
Value::Record { val, span } => {
let span = *span;
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
if let Some(found) = cols.iter().zip(vals.iter()).rev().find(|x| {
if let Some(found) = val.iter().rev().find(|x| {
if insensitive {
x.0.to_lowercase() == column_name.to_lowercase()
} else {
@@ -998,7 +988,7 @@ impl Value {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else {
if from_user_input {
if let Some(suggestion) = did_you_mean(&cols, column_name) {
if let Some(suggestion) = did_you_mean(&val.cols, column_name) {
return Err(ShellError::DidYouMean(suggestion, *origin_span));
}
}
@@ -1126,12 +1116,12 @@ impl Value {
Value::List { vals, .. } => {
for val in vals.iter_mut() {
match val {
Value::Record { cols, vals, .. } => {
Value::Record { val: record, .. } => {
let mut found = false;
for col in cols.iter().zip(vals.iter_mut()) {
if col.0 == col_name {
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
col.1.upsert_data_at_cell_path(
val.upsert_data_at_cell_path(
&cell_path[1..],
new_val.clone(),
)?
@@ -1139,15 +1129,11 @@ impl Value {
}
if !found {
if cell_path.len() == 1 {
cols.push(col_name.clone());
vals.push(new_val);
record.push(col_name, new_val);
break;
} else {
let mut new_col = Value::Record {
cols: vec![],
vals: vec![],
span: new_val.span()?,
};
let mut new_col =
Value::record(Record::new(), new_val.span()?);
new_col.upsert_data_at_cell_path(
&cell_path[1..],
new_val,
@@ -1168,30 +1154,25 @@ impl Value {
}
}
}
Value::Record { cols, vals, .. } => {
Value::Record { val: record, .. } => {
let mut found = false;
for col in cols.iter().zip(vals.iter_mut()) {
if col.0 == col_name {
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
col.1
.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
val.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
}
}
if !found {
cols.push(col_name.clone());
if cell_path.len() == 1 {
vals.push(new_val);
let new_col = if cell_path.len() == 1 {
new_val
} else {
let mut new_col = Value::Record {
cols: vec![],
vals: vec![],
span: new_val.span()?,
};
let mut new_col = Value::record(Record::new(), new_val.span()?);
new_col.upsert_data_at_cell_path(&cell_path[1..], new_val)?;
vals.push(new_col);
}
new_col
};
record.push(col_name, new_col);
}
}
Value::LazyRecord { val, .. } => {
@@ -1275,15 +1256,14 @@ impl Value {
for val in vals.iter_mut() {
match val {
Value::Record {
cols,
vals,
val: record,
span: v_span,
} => {
let mut found = false;
for col in cols.iter().zip(vals.iter_mut()) {
if col.0 == col_name {
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
col.1.update_data_at_cell_path(
val.update_data_at_cell_path(
&cell_path[1..],
new_val.clone(),
)?
@@ -1309,18 +1289,15 @@ impl Value {
}
}
Value::Record {
cols,
vals,
val: record,
span: v_span,
} => {
let mut found = false;
for col in cols.iter().zip(vals.iter_mut()) {
if col.0 == col_name {
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
col.1
.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
val.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
}
}
if !found {
@@ -1392,16 +1369,15 @@ impl Value {
for val in vals.iter_mut() {
match val {
Value::Record {
cols,
vals,
val: record,
span: v_span,
} => {
let mut found = false;
let mut index = 0;
cols.retain_mut(|col| {
record.cols.retain_mut(|col| {
if col == col_name {
found = true;
vals.remove(index);
record.vals.remove(index);
false
} else {
index += 1;
@@ -1428,18 +1404,21 @@ impl Value {
Ok(())
}
Value::Record {
cols,
vals,
val: record,
span: v_span,
} => {
let mut found = false;
for (i, col) in cols.clone().iter().enumerate() {
let mut index = 0;
record.cols.retain_mut(|col| {
if col == col_name {
cols.remove(i);
vals.remove(i);
found = true;
record.vals.remove(index);
false
} else {
index += 1;
true
}
}
});
if !found && !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
@@ -1501,15 +1480,14 @@ impl Value {
for val in vals.iter_mut() {
match val {
Value::Record {
cols,
vals,
val: record,
span: v_span,
} => {
let mut found = false;
for col in cols.iter().zip(vals.iter_mut()) {
if col.0 == col_name {
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
col.1.remove_data_at_cell_path(&cell_path[1..])?
val.remove_data_at_cell_path(&cell_path[1..])?
}
}
if !found && !optional {
@@ -1532,17 +1510,15 @@ impl Value {
Ok(())
}
Value::Record {
cols,
vals,
val: record,
span: v_span,
} => {
let mut found = false;
for col in cols.iter().zip(vals.iter_mut()) {
if col.0 == col_name {
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
col.1.remove_data_at_cell_path(&cell_path[1..])?
val.remove_data_at_cell_path(&cell_path[1..])?
}
}
if !found && !optional {
@@ -1613,12 +1589,11 @@ impl Value {
for val in vals.iter_mut() {
match val {
Value::Record {
cols,
vals,
val: record,
span: v_span,
} => {
for col in cols.iter().zip(vals.iter_mut()) {
if col.0 == col_name {
for (col, val) in record.iter_mut() {
if col == col_name {
if cell_path.len() == 1 {
return Err(ShellError::ColumnAlreadyExists {
col_name: col_name.to_string(),
@@ -1626,7 +1601,7 @@ impl Value {
src_span: *v_span,
});
} else {
return col.1.insert_data_at_cell_path(
return val.insert_data_at_cell_path(
&cell_path[1..],
new_val,
head_span,
@@ -1635,8 +1610,7 @@ impl Value {
}
}
cols.push(col_name.clone());
vals.push(new_val.clone());
record.push(col_name, new_val.clone());
}
// SIGH...
Value::Error { error } => return Err(*error.clone()),
@@ -1652,12 +1626,11 @@ impl Value {
}
}
Value::Record {
cols,
vals,
val: record,
span: v_span,
} => {
for col in cols.iter().zip(vals.iter_mut()) {
if col.0 == col_name {
for (col, val) in record.iter_mut() {
if col == col_name {
if cell_path.len() == 1 {
return Err(ShellError::ColumnAlreadyExists {
col_name: col_name.to_string(),
@@ -1665,7 +1638,7 @@ impl Value {
src_span: *v_span,
});
} else {
return col.1.insert_data_at_cell_path(
return val.insert_data_at_cell_path(
&cell_path[1..],
new_val,
head_span,
@@ -1674,8 +1647,7 @@ impl Value {
}
}
cols.push(col_name.clone());
vals.push(new_val);
record.push(col_name, new_val);
}
Value::LazyRecord { val, span } => {
// convert to Record first.
@@ -1734,7 +1706,7 @@ impl Value {
pub fn columns(&self) -> &[String] {
match self {
Value::Record { cols, .. } => cols,
Value::Record { val, .. } => &val.cols,
_ => &[],
}
}
@@ -1777,18 +1749,8 @@ impl Value {
}
}
pub fn record(cols: Vec<String>, vals: Vec<Value>, span: Span) -> Value {
Value::Record { cols, vals, span }
}
pub fn record_from_hashmap(map: &HashMap<String, Value>, span: Span) -> Value {
let mut cols = vec![];
let mut vals = vec![];
for (key, val) in map.iter() {
cols.push(key.clone());
vals.push(val.clone());
}
Value::record(cols, vals, span)
pub fn record(val: Record, span: Span) -> Value {
Value::Record { val, span }
}
pub fn list(vals: Vec<Value>, span: Span) -> Value {
@@ -1894,12 +1856,8 @@ impl Value {
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_record(cols: Vec<impl Into<String>>, vals: Vec<Value>) -> Value {
Value::record(
cols.into_iter().map(|s| s.into()).collect(),
vals,
Span::test_data(),
)
pub fn test_record(val: Record) -> Value {
Value::record(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
@@ -2148,14 +2106,7 @@ impl PartialOrd for Value {
Value::CustomValue { .. } => Some(Ordering::Less),
Value::MatchPattern { .. } => Some(Ordering::Less),
},
(
Value::Record {
cols: lhs_cols,
vals: lhs_vals,
..
},
rhs,
) => match rhs {
(Value::Record { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
@@ -2164,18 +2115,12 @@ impl PartialOrd for Value {
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Record {
cols: rhs_cols,
vals: rhs_vals,
..
} => {
Value::Record { val: rhs, .. } => {
// reorder cols and vals to make more logically compare.
// more general, if two record have same col and values,
// the order of cols shouldn't affect the equal property.
let (lhs_cols_ordered, lhs_vals_ordered) =
reorder_record_inner(lhs_cols, lhs_vals);
let (rhs_cols_ordered, rhs_vals_ordered) =
reorder_record_inner(rhs_cols, rhs_vals);
let (lhs_cols_ordered, lhs_vals_ordered) = reorder_record_inner(lhs);
let (rhs_cols_ordered, rhs_vals_ordered) = reorder_record_inner(rhs);
let result = lhs_cols_ordered.partial_cmp(&rhs_cols_ordered);
if result == Some(Ordering::Equal) {
@@ -3190,8 +3135,8 @@ impl Value {
val: rhs.contains(lhs),
span,
}),
(Value::String { val: lhs, .. }, Value::Record { cols: rhs, .. }) => Ok(Value::Bool {
val: rhs.contains(lhs),
(Value::String { val: lhs, .. }, Value::Record { val: rhs, .. }) => Ok(Value::Bool {
val: rhs.cols.contains(lhs),
span,
}),
(Value::String { .. } | Value::Int { .. }, Value::CellPath { val: rhs, .. }) => {
@@ -3247,8 +3192,8 @@ impl Value {
val: !rhs.contains(lhs),
span,
}),
(Value::String { val: lhs, .. }, Value::Record { cols: rhs, .. }) => Ok(Value::Bool {
val: !rhs.contains(lhs),
(Value::String { val: lhs, .. }, Value::Record { val: rhs, .. }) => Ok(Value::Bool {
val: !rhs.cols.contains(lhs),
span,
}),
(Value::String { .. } | Value::Int { .. }, Value::CellPath { val: rhs, .. }) => {
@@ -3648,36 +3593,10 @@ impl Value {
}
}
fn reorder_record_inner(cols: &[String], vals: &[Value]) -> (Vec<String>, Vec<Value>) {
let mut kv_pairs =
iter::zip(cols.to_owned(), vals.to_owned()).collect::<Vec<(String, Value)>>();
kv_pairs.sort_by(|a, b| {
a.0.partial_cmp(&b.0)
.expect("Columns should support compare")
});
let (mut cols, mut vals) = (vec![], vec![]);
for (col, val) in kv_pairs {
cols.push(col);
vals.push(val);
}
(cols, vals)
}
/// Create a Value::Record from a spanned hashmap
impl From<Spanned<HashMap<String, Value>>> for Value {
fn from(input: Spanned<HashMap<String, Value>>) -> Self {
let span = input.span;
let (cols, vals) = input
.item
.into_iter()
.fold((vec![], vec![]), |mut acc, (k, v)| {
acc.0.push(k);
acc.1.push(v);
acc
});
Value::Record { cols, vals, span }
}
fn reorder_record_inner(record: &Record) -> (Vec<&String>, Vec<&Value>) {
let mut kv_pairs = record.iter().collect::<Vec<_>>();
kv_pairs.sort_by_key(|(col, _)| *col);
kv_pairs.into_iter().unzip()
}
fn type_compatible(a: Type, b: Type) -> bool {
@@ -3688,23 +3607,6 @@ fn type_compatible(a: Type, b: Type) -> bool {
matches!((a, b), (Type::Int, Type::Float) | (Type::Float, Type::Int))
}
/// Create a Value::Record from a spanned indexmap
impl From<Spanned<IndexMap<String, Value>>> for Value {
fn from(input: Spanned<IndexMap<String, Value>>) -> Self {
let span = input.span;
let (cols, vals) = input
.item
.into_iter()
.fold((vec![], vec![]), |mut acc, (k, v)| {
acc.0.push(k);
acc.1.push(v);
acc
});
Value::Record { cols, vals, span }
}
}
/// Is the given year a leap year?
#[allow(clippy::nonminimal_bool)]
pub fn is_leap_year(year: i32) -> bool {
@@ -3985,7 +3887,7 @@ fn get_filesize_format(format_value: &str, filesize_metric: Option<bool>) -> (By
#[cfg(test)]
mod tests {
use super::{Span, Value};
use super::{Record, Span, Value};
mod is_empty {
use super::*;
@@ -4013,26 +3915,24 @@ mod tests {
#[test]
fn test_record() {
let no_columns_nor_cell_values = Value::Record {
cols: vec![],
vals: vec![],
span: Span::unknown(),
};
let one_column_and_one_cell_value_with_empty_strings = Value::Record {
let no_columns_nor_cell_values = Value::test_record(Record::new());
let one_column_and_one_cell_value_with_empty_strings = Value::test_record(Record {
cols: vec![String::from("")],
vals: vec![Value::string("", Span::unknown())],
span: Span::unknown(),
};
let one_column_with_a_string_and_one_cell_value_with_empty_string = Value::Record {
cols: vec![String::from("column")],
vals: vec![Value::string("", Span::unknown())],
span: Span::unknown(),
};
let one_column_with_empty_string_and_one_value_with_a_string = Value::Record {
cols: vec![String::from("")],
vals: vec![Value::string("text", Span::unknown())],
span: Span::unknown(),
};
});
let one_column_with_a_string_and_one_cell_value_with_empty_string =
Value::test_record(Record {
cols: vec![String::from("column")],
vals: vec![Value::string("", Span::unknown())],
});
let one_column_with_empty_string_and_one_value_with_a_string =
Value::test_record(Record {
cols: vec![String::from("")],
vals: vec![Value::string("text", Span::unknown())],
});
assert!(no_columns_nor_cell_values.is_empty());
assert!(!one_column_and_one_cell_value_with_empty_strings.is_empty());

View File

@@ -0,0 +1,99 @@
use crate::Value;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Record {
pub cols: Vec<String>,
pub vals: Vec<Value>,
}
impl Record {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
cols: Vec::with_capacity(capacity),
vals: Vec::with_capacity(capacity),
}
}
pub fn iter(&self) -> Iter {
self.into_iter()
}
pub fn iter_mut(&mut self) -> IterMut {
self.into_iter()
}
pub fn is_empty(&self) -> bool {
self.cols.is_empty() || self.vals.is_empty()
}
pub fn len(&self) -> usize {
usize::min(self.cols.len(), self.vals.len())
}
pub fn push(&mut self, col: impl Into<String>, val: Value) {
self.cols.push(col.into());
self.vals.push(val);
}
}
impl FromIterator<(String, Value)> for Record {
fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
let (cols, vals) = iter.into_iter().unzip();
Self { cols, vals }
}
}
pub type IntoIter = std::iter::Zip<std::vec::IntoIter<String>, std::vec::IntoIter<Value>>;
impl IntoIterator for Record {
type Item = (String, Value);
type IntoIter = IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.cols.into_iter().zip(self.vals)
}
}
pub type Iter<'a> = std::iter::Zip<std::slice::Iter<'a, String>, std::slice::Iter<'a, Value>>;
impl<'a> IntoIterator for &'a Record {
type Item = (&'a String, &'a Value);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.cols.iter().zip(&self.vals)
}
}
pub type IterMut<'a> = std::iter::Zip<std::slice::Iter<'a, String>, std::slice::IterMut<'a, Value>>;
impl<'a> IntoIterator for &'a mut Record {
type Item = (&'a String, &'a mut Value);
type IntoIter = IterMut<'a>;
fn into_iter(self) -> Self::IntoIter {
self.cols.iter().zip(&mut self.vals)
}
}
#[macro_export]
macro_rules! record {
{$($col:expr => $val:expr),+ $(,)?} => {
$crate::Record {
cols: vec![$($col.into(),)+],
vals: vec![$($val,)+]
}
};
{} => {
$crate::Record::new()
};
}