extendr_api/robj/
try_from_robj.rs

1//! There are various ways an [`Robj`] may be converted into different types `T`.
2//!
3//! This module defines these conversions on `&Robj`. Due to internal reference
4//! counting measure of [`ownership`]-module, it is cheaper to copy `&Robj`,
5//! than copying `Robj`, as the latter will incur an increase in reference counting.
6//!
7//!
8//! [`ownership`]: crate::ownership
9use super::*;
10use crate as extendr_api;
11use crate::conversions::try_into_int::FloatToInt;
12
13macro_rules! impl_try_from_scalar_integer {
14    ($t:ty) => {
15        impl TryFrom<&Robj> for $t {
16            type Error = Error;
17
18            /// Convert a numeric object to an integer value.
19            fn try_from(robj: &Robj) -> Result<Self> {
20                // Check if the value is a scalar
21                match robj.len() {
22                    0 => return Err(Error::ExpectedNonZeroLength(robj.clone())),
23                    1 => {}
24                    _ => return Err(Error::ExpectedScalar(robj.clone())),
25                };
26
27                // Check if the value is not a missing value
28                if robj.is_na() {
29                    return Err(Error::MustNotBeNA(robj.clone()));
30                }
31
32                // If the conversion is int-to-int, check the limits. This
33                // needs to be done by `TryFrom` because the conversion by `as`
34                // is problematic when converting a negative value to unsigned
35                // integer types (e.g. `-1i32 as u8` becomes 255).
36                if let Some(v) = robj.as_integer() {
37                    return Self::try_from(v).map_err(|_| Error::OutOfLimits(robj.clone()));
38                }
39
40                // If the conversion is float-to-int, check if the value is
41                // integer-like (i.e., an integer, or a float representing a
42                // whole number).
43                if let Some(v) = robj.as_real() {
44                    return v
45                        .try_into_int()
46                        .map_err(|conv_err| Error::ExpectedWholeNumber(robj.clone(), conv_err));
47                }
48
49                Err(Error::ExpectedNumeric(robj.clone()))
50            }
51        }
52    };
53}
54
55macro_rules! impl_try_from_scalar_real {
56    ($t:ty) => {
57        impl TryFrom<&Robj> for $t {
58            type Error = Error;
59
60            /// Convert a numeric object to a real value.
61            fn try_from(robj: &Robj) -> Result<Self> {
62                // Check if the value is a scalar
63                match robj.len() {
64                    0 => return Err(Error::ExpectedNonZeroLength(robj.clone())),
65                    1 => {}
66                    _ => return Err(Error::ExpectedScalar(robj.clone())),
67                };
68
69                // Check if the value is not a missing value
70                if robj.is_na() {
71                    return Err(Error::MustNotBeNA(robj.clone()));
72                }
73
74                // `<Robj>::as_xxx()` methods can work only when the underlying
75                // `SEXP` is the corresponding type, so we cannot use `as_real()`
76                // directly on `INTSXP`.
77                if let Some(v) = robj.as_real() {
78                    // f64 to f32 and f64 to f64 is always safe.
79                    return Ok(v as Self);
80                }
81                if let Some(v) = robj.as_integer() {
82                    // An i32 R integer can be represented exactly by f64, but might be truncated in f32.
83                    return Ok(v as Self);
84                }
85
86                Err(Error::ExpectedNumeric(robj.clone()))
87            }
88        }
89    };
90}
91
92impl_try_from_scalar_integer!(u8);
93impl_try_from_scalar_integer!(u16);
94impl_try_from_scalar_integer!(u32);
95impl_try_from_scalar_integer!(u64);
96impl_try_from_scalar_integer!(usize);
97impl_try_from_scalar_integer!(i8);
98impl_try_from_scalar_integer!(i16);
99impl_try_from_scalar_integer!(i32);
100impl_try_from_scalar_integer!(i64);
101impl_try_from_scalar_integer!(isize);
102impl_try_from_scalar_real!(f32);
103impl_try_from_scalar_real!(f64);
104
105impl TryFrom<&Robj> for bool {
106    type Error = Error;
107
108    /// Convert an LGLSXP object into a boolean.
109    /// NAs are not allowed.
110    fn try_from(robj: &Robj) -> Result<Self> {
111        if robj.is_na() {
112            Err(Error::MustNotBeNA(robj.clone()))
113        } else {
114            Ok(<Rbool>::try_from(robj)?.is_true())
115        }
116    }
117}
118
119impl TryFrom<&Robj> for &str {
120    type Error = Error;
121
122    /// Convert a scalar STRSXP object into a string slice.
123    /// NAs are not allowed.
124    fn try_from(robj: &Robj) -> Result<Self> {
125        if robj.is_na() {
126            return Err(Error::MustNotBeNA(robj.clone()));
127        }
128        match robj.len() {
129            0 => Err(Error::ExpectedNonZeroLength(robj.clone())),
130            1 => {
131                if let Some(s) = robj.as_str() {
132                    Ok(s)
133                } else {
134                    Err(Error::ExpectedString(robj.clone()))
135                }
136            }
137            _ => Err(Error::ExpectedScalar(robj.clone())),
138        }
139    }
140}
141
142impl TryFrom<&Robj> for String {
143    type Error = Error;
144
145    /// Convert an scalar STRSXP object into a String.
146    /// Note: Unless you plan to store the result, use a string slice instead.
147    /// NAs are not allowed.
148    fn try_from(robj: &Robj) -> Result<Self> {
149        <&str>::try_from(robj).map(|s| s.to_string())
150    }
151}
152
153impl TryFrom<&Robj> for Vec<i32> {
154    type Error = Error;
155
156    /// Convert an INTSXP object into a vector of i32 (integer).
157    /// Note: Unless you plan to store the result, use a slice instead.
158    /// Use `value.is_na()` to detect NA values.
159    fn try_from(robj: &Robj) -> Result<Self> {
160        if let Some(v) = robj.as_typed_slice() {
161            // TODO: check NAs
162            Ok(Vec::from(v))
163        } else {
164            Err(Error::ExpectedInteger(robj.clone()))
165        }
166    }
167}
168
169impl TryFrom<&Robj> for Vec<f64> {
170    type Error = Error;
171
172    /// Convert a REALSXP object into a vector of f64 (double precision floating point).
173    /// Note: Unless you plan to store the result, use a slice instead.
174    /// Use `value.is_na()` to detect NA values.
175    fn try_from(robj: &Robj) -> Result<Self> {
176        if let Some(v) = robj.as_typed_slice() {
177            // TODO: check NAs
178            Ok(Vec::from(v))
179        } else {
180            Err(Error::ExpectedReal(robj.clone()))
181        }
182    }
183}
184
185impl TryFrom<&Robj> for Vec<u8> {
186    type Error = Error;
187
188    /// Convert a RAWSXP object into a vector of bytes.
189    /// Note: Unless you plan to store the result, use a slice instead.
190    fn try_from(robj: &Robj) -> Result<Self> {
191        if let Some(v) = robj.as_typed_slice() {
192            Ok(Vec::from(v))
193        } else {
194            Err(Error::ExpectedRaw(robj.clone()))
195        }
196    }
197}
198
199impl TryFrom<&Robj> for Vec<Rint> {
200    type Error = Error;
201
202    /// Convert an INTSXP object into a vector of i32 (integer).
203    /// Note: Unless you plan to store the result, use a slice instead.
204    /// Use `value.is_na()` to detect NA values.
205    fn try_from(robj: &Robj) -> Result<Self> {
206        if let Some(v) = robj.as_typed_slice() {
207            Ok(Vec::from(v))
208        } else {
209            Err(Error::ExpectedInteger(robj.clone()))
210        }
211    }
212}
213
214impl TryFrom<&Robj> for Vec<Rfloat> {
215    type Error = Error;
216
217    /// Convert a REALSXP object into a vector of f64 (double precision floating point).
218    /// Note: Unless you plan to store the result, use a slice instead.
219    /// Use `value.is_na()` to detect NA values.
220    fn try_from(robj: &Robj) -> Result<Self> {
221        if let Some(v) = robj.as_typed_slice() {
222            Ok(Vec::from(v))
223        } else {
224            Err(Error::ExpectedReal(robj.clone()))
225        }
226    }
227}
228
229impl TryFrom<&Robj> for Vec<Rbool> {
230    type Error = Error;
231
232    /// Convert a LGLSXP object into a vector of Rbool (tri-state booleans).
233    /// Note: Unless you plan to store the result, use a slice instead.
234    /// Use `value.is_na()` to detect NA values.
235    fn try_from(robj: &Robj) -> Result<Self> {
236        if let Some(v) = robj.as_typed_slice() {
237            Ok(Vec::from(v))
238        } else {
239            Err(Error::ExpectedInteger(robj.clone()))
240        }
241    }
242}
243
244impl TryFrom<&Robj> for Vec<Rcplx> {
245    type Error = Error;
246
247    /// Convert a complex object into a vector of Rcplx.
248    fn try_from(robj: &Robj) -> Result<Self> {
249        if let Some(v) = robj.as_typed_slice() {
250            Ok(Vec::from(v))
251        } else {
252            Err(Error::ExpectedComplex(robj.clone()))
253        }
254    }
255}
256
257impl TryFrom<&Robj> for Vec<String> {
258    type Error = Error;
259
260    /// Convert a STRSXP object into a vector of `String`s.
261    /// Note: Unless you plan to store the result, use a slice instead.
262    fn try_from(robj: &Robj) -> Result<Self> {
263        if let Some(iter) = robj.as_str_iter() {
264            // check for NA's in the string vector
265            if iter.clone().any(|s| s.is_na()) {
266                Err(Error::MustNotBeNA(robj.clone()))
267            } else {
268                Ok(iter.map(|s| s.to_string()).collect::<Vec<String>>())
269            }
270        } else {
271            Err(Error::ExpectedString(robj.clone()))
272        }
273    }
274}
275
276impl TryFrom<&Robj> for &[i32] {
277    type Error = Error;
278
279    /// Convert an INTSXP object into a slice of i32 (integer).
280    /// Use `value.is_na()` to detect NA values.
281    fn try_from(robj: &Robj) -> Result<Self> {
282        robj.as_typed_slice()
283            .ok_or_else(|| Error::ExpectedInteger(robj.clone()))
284    }
285}
286
287impl TryFrom<&Robj> for &[Rint] {
288    type Error = Error;
289
290    /// Convert an integer object into a slice of Rint (tri-state booleans).
291    /// Use `value.is_na()` to detect NA values.
292    fn try_from(robj: &Robj) -> Result<Self> {
293        robj.as_typed_slice()
294            .ok_or_else(|| Error::ExpectedInteger(robj.clone()))
295    }
296}
297
298impl TryFrom<&Robj> for &[Rfloat] {
299    type Error = Error;
300
301    /// Convert a doubles object into a slice of Rfloat (tri-state booleans).
302    /// Use `value.is_na()` to detect NA values.
303    fn try_from(robj: &Robj) -> Result<Self> {
304        robj.as_typed_slice()
305            .ok_or_else(|| Error::ExpectedReal(robj.clone()))
306    }
307}
308
309impl TryFrom<&Robj> for &[Rbool] {
310    type Error = Error;
311
312    /// Convert a logical object into a slice of Rbool (tri-state booleans).
313    /// Use `value.is_na()` to detect NA values.
314    fn try_from(robj: &Robj) -> Result<Self> {
315        robj.as_typed_slice()
316            .ok_or_else(|| Error::ExpectedLogical(robj.clone()))
317    }
318}
319
320impl TryFrom<&Robj> for &[Rcplx] {
321    type Error = Error;
322
323    /// Convert a complex object into a slice of Rcplx
324    /// Use `value.is_na()` to detect NA values.
325    fn try_from(robj: &Robj) -> Result<Self> {
326        robj.as_typed_slice()
327            .ok_or_else(|| Error::ExpectedComplex(robj.clone()))
328    }
329}
330
331impl TryFrom<&Robj> for &[u8] {
332    type Error = Error;
333
334    /// Convert a RAWSXP object into a slice of bytes.
335    fn try_from(robj: &Robj) -> Result<Self> {
336        robj.as_typed_slice()
337            .ok_or_else(|| Error::ExpectedRaw(robj.clone()))
338    }
339}
340
341impl TryFrom<&Robj> for &[f64] {
342    type Error = Error;
343
344    /// Convert a REALSXP object into a slice of f64 (double precision floating point).
345    /// Use `value.is_na()` to detect NA values.
346    fn try_from(robj: &Robj) -> Result<Self> {
347        robj.as_typed_slice()
348            .ok_or_else(|| Error::ExpectedReal(robj.clone()))
349    }
350}
351
352impl TryFrom<&mut Robj> for &mut [i32] {
353    type Error = Error;
354
355    /// Convert an INTSXP object into a mutable slice of i32 (integer).
356    /// Use `value.is_na()` to detect NA values.
357    fn try_from(robj: &mut Robj) -> Result<Self> {
358        robj.as_typed_slice_mut()
359            .ok_or_else(|| Error::ExpectedInteger(robj.clone()))
360    }
361}
362
363impl TryFrom<&mut Robj> for &mut [Rint] {
364    type Error = Error;
365
366    /// Convert an integer object into a mutable slice of Rint (tri-state booleans).
367    /// Use `value.is_na()` to detect NA values.
368    fn try_from(robj: &mut Robj) -> Result<Self> {
369        robj.as_typed_slice_mut()
370            .ok_or_else(|| Error::ExpectedInteger(robj.clone()))
371    }
372}
373
374impl TryFrom<&mut Robj> for &mut [Rfloat] {
375    type Error = Error;
376
377    /// Convert a doubles object into a mutable slice of Rfloat (tri-state booleans).
378    /// Use `value.is_na()` to detect NA values.
379    fn try_from(robj: &mut Robj) -> Result<Self> {
380        robj.as_typed_slice_mut()
381            .ok_or_else(|| Error::ExpectedReal(robj.clone()))
382    }
383}
384
385impl TryFrom<&mut Robj> for &mut [Rbool] {
386    type Error = Error;
387
388    /// Convert a logical object into a mutable slice of Rbool (tri-state booleans).
389    /// Use `value.is_na()` to detect NA values.
390    fn try_from(robj: &mut Robj) -> Result<Self> {
391        robj.as_typed_slice_mut()
392            .ok_or_else(|| Error::ExpectedLogical(robj.clone()))
393    }
394}
395
396impl TryFrom<&mut Robj> for &mut [Rcplx] {
397    type Error = Error;
398
399    /// Convert a complex object into a mutable slice of Rcplx
400    /// Use `value.is_na()` to detect NA values.
401    fn try_from(robj: &mut Robj) -> Result<Self> {
402        robj.as_typed_slice_mut()
403            .ok_or_else(|| Error::ExpectedComplex(robj.clone()))
404    }
405}
406
407impl TryFrom<&mut Robj> for &mut [u8] {
408    type Error = Error;
409
410    /// Convert a RAWSXP object into a mutable slice of bytes.
411    fn try_from(robj: &mut Robj) -> Result<Self> {
412        robj.as_typed_slice_mut()
413            .ok_or_else(|| Error::ExpectedRaw(robj.clone()))
414    }
415}
416
417impl TryFrom<&mut Robj> for &mut [f64] {
418    type Error = Error;
419
420    /// Convert a REALSXP object into a mutable slice of f64 (double precision floating point).
421    /// Use `value.is_na()` to detect NA values.
422    fn try_from(robj: &mut Robj) -> Result<Self> {
423        robj.as_typed_slice_mut()
424            .ok_or_else(|| Error::ExpectedReal(robj.clone()))
425    }
426}
427
428impl TryFrom<&Robj> for Rcplx {
429    type Error = Error;
430
431    fn try_from(robj: &Robj) -> Result<Self> {
432        // Check if the value is a scalar
433        match robj.len() {
434            0 => return Err(Error::ExpectedNonZeroLength(robj.clone())),
435            1 => {}
436            _ => return Err(Error::ExpectedScalar(robj.clone())),
437        };
438
439        // Check if the value is not a missing value.
440        if robj.is_na() {
441            return Ok(Rcplx::na());
442        }
443
444        // This should always work, NA is handled above.
445        if let Some(v) = robj.as_real() {
446            return Ok(Rcplx::from(v));
447        }
448
449        // Any integer (32 bit) can be represented as f64,
450        // this always works.
451        if let Some(v) = robj.as_integer() {
452            return Ok(Rcplx::from(v as f64));
453        }
454
455        // Complex slices return their first element.
456        if let Some(s) = robj.as_typed_slice() {
457            return Ok(s[0]);
458        }
459
460        Err(Error::ExpectedComplex(robj.clone()))
461    }
462}
463
464// Convert TryFrom<&Robj> into TryFrom<Robj>. Sadly, we are unable to make a blanket
465// conversion using GetSexp with the current version of Rust.
466macro_rules! impl_try_from_robj {
467    (&mut [$type:ty]) => {
468        impl TryFrom<Robj> for &mut [$type] {
469            type Error = Error;
470
471            fn try_from(mut robj: Robj) -> Result<Self> {
472                Self::try_from(&mut robj)
473            }
474        }
475
476        impl TryFrom<&mut Robj> for Option<&mut [$type]> {
477            type Error = Error;
478
479            fn try_from(robj: &mut Robj) -> Result<Self> {
480                if robj.is_null() || robj.is_na() {
481                    Ok(None)
482                } else {
483                    Ok(Some(<&mut [$type]>::try_from(robj)?))
484                }
485            }
486        }
487
488        impl TryFrom<Robj> for Option<&mut [$type]> {
489            type Error = Error;
490
491            fn try_from(mut robj: Robj) -> Result<Self> {
492                Self::try_from(&mut robj)
493            }
494        }
495    };
496    ($(@generics<$generics:tt>)? $type:ty $(where $($where_clause:tt)*)?) => {
497        impl$(<$generics>)? TryFrom<Robj> for $type $(where $($where_clause)*)? {
498            type Error = Error;
499
500            fn try_from(robj: Robj) -> Result<Self> {
501                Self::try_from(&robj)
502            }
503        }
504
505        impl$(<$generics>)? TryFrom<&Robj> for Option<$type> $(where $($where_clause)*)? {
506            type Error = Error;
507
508            fn try_from(robj: &Robj) -> Result<Self> {
509                if robj.is_null() || robj.is_na() {
510                    Ok(None)
511                } else {
512                    Ok(Some(<$type>::try_from(robj)?))
513                }
514            }
515        }
516
517        impl$(<$generics>)? TryFrom<Robj> for Option<$type> $(where $($where_clause)*)? {
518            type Error = Error;
519
520            fn try_from(robj: Robj) -> Result<Self> {
521                Self::try_from(&robj)
522            }
523        }
524    };
525}
526#[rustfmt::skip]
527impl_try_from_robj!(u8);
528impl_try_from_robj!(u16);
529impl_try_from_robj!(u32);
530impl_try_from_robj!(u64);
531impl_try_from_robj!(usize);
532
533impl_try_from_robj!(i8);
534impl_try_from_robj!(i16);
535impl_try_from_robj!(i32);
536impl_try_from_robj!(i64);
537impl_try_from_robj!(isize);
538
539impl_try_from_robj!(bool);
540
541impl_try_from_robj!(Rint);
542impl_try_from_robj!(Rfloat);
543impl_try_from_robj!(Rbool);
544impl_try_from_robj!(Rcplx);
545
546impl_try_from_robj!(f32);
547impl_try_from_robj!(f64);
548
549impl_try_from_robj!(Vec::<String>);
550impl_try_from_robj!(Vec::<Rint>);
551impl_try_from_robj!(Vec::<Rfloat>);
552impl_try_from_robj!(Vec::<Rbool>);
553impl_try_from_robj!(Vec::<Rcplx>);
554impl_try_from_robj!(Vec::<u8>);
555impl_try_from_robj!(Vec::<i32>);
556impl_try_from_robj!(Vec::<f64>);
557
558impl_try_from_robj!(&[Rint]);
559impl_try_from_robj!(&[Rfloat]);
560impl_try_from_robj!(&[Rbool]);
561impl_try_from_robj!(&[Rcplx]);
562impl_try_from_robj!(&[u8]);
563impl_try_from_robj!(&[i32]);
564impl_try_from_robj!(&[f64]);
565
566impl_try_from_robj!(&mut [Rint]);
567impl_try_from_robj!(&mut [Rfloat]);
568impl_try_from_robj!(&mut [Rbool]);
569impl_try_from_robj!(&mut [Rcplx]);
570impl_try_from_robj!(&mut [u8]);
571impl_try_from_robj!(&mut [i32]);
572impl_try_from_robj!(&mut [f64]);
573
574impl_try_from_robj!(&str);
575impl_try_from_robj!(String);
576
577impl_try_from_robj!(@generics<T> HashMap::<&str, T> where T: TryFrom<Robj, Error = error::Error>);
578impl_try_from_robj!(@generics<T> HashMap::<String,T> where T: TryFrom<Robj, Error = error::Error>);
579
580impl_try_from_robj!(HashMap::<&str, Robj>);
581impl_try_from_robj!(HashMap::<String, Robj>);
582
583// NOTE: this is included for compatibility with previously defined `FromRobj`
584// One should prefer `List::from_hashmap` instead,
585// and this `impl` should be deprecated next.
586
587impl<T> TryFrom<&Robj> for HashMap<&str, T>
588where
589    T: TryFrom<Robj, Error = error::Error>,
590{
591    type Error = Error;
592
593    fn try_from(value: &Robj) -> Result<Self> {
594        let value: List = value.try_into()?;
595
596        let value = value
597            .iter()
598            .map(|(name, value)| -> Result<(&str, T)> { value.try_into().map(|x| (name, x)) })
599            .collect::<Result<HashMap<_, _>>>()?;
600
601        Ok(value)
602    }
603}
604
605impl<T> TryFrom<&Robj> for HashMap<String, T>
606where
607    T: TryFrom<Robj, Error = error::Error>,
608{
609    type Error = Error;
610    fn try_from(value: &Robj) -> Result<Self> {
611        let value: HashMap<&str, _> = value.try_into()?;
612        Ok(value.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
613    }
614}
615
616macro_rules! impl_try_from_robj_for_arrays {
617    ($slice_type:ty) => {
618        impl<const N: usize> TryFrom<&Robj> for [$slice_type; N] {
619            type Error = Error;
620
621            fn try_from(value: &Robj) -> Result<Self> {
622                let value: &[$slice_type] = value.try_into()?;
623                if value.len() != N {
624                    return Err(Error::ExpectedLength(N));
625                }
626                let value: Self = value
627                    .try_into()
628                    .map_err(|error| format!("{}", error).to_string())?;
629                Ok(value)
630            }
631        }
632
633        // TODO: the following can be integrated into `impl_try_from_robj` later
634
635        impl<const N: usize> TryFrom<Robj> for [$slice_type; N] {
636            type Error = Error;
637
638            fn try_from(robj: Robj) -> Result<Self> {
639                Self::try_from(&robj)
640            }
641        }
642
643        impl<const N: usize> TryFrom<&Robj> for Option<[$slice_type; N]> {
644            type Error = Error;
645
646            fn try_from(robj: &Robj) -> Result<Self> {
647                if robj.is_null() || robj.is_na() {
648                    Ok(None)
649                } else {
650                    Ok(Some(<[$slice_type; N]>::try_from(robj)?))
651                }
652            }
653        }
654
655        impl<const N: usize> TryFrom<Robj> for Option<[$slice_type; N]> {
656            type Error = Error;
657
658            fn try_from(robj: Robj) -> Result<Self> {
659                Self::try_from(&robj)
660            }
661        }
662    };
663}
664
665impl_try_from_robj_for_arrays!(Rint);
666impl_try_from_robj_for_arrays!(Rfloat);
667impl_try_from_robj_for_arrays!(Rbool);
668impl_try_from_robj_for_arrays!(Rcplx);
669impl_try_from_robj_for_arrays!(u8);
670impl_try_from_robj_for_arrays!(i32);
671impl_try_from_robj_for_arrays!(f64);
672
673// Choosing arity 12.. As the Rust compiler did for these [Tuple to array conversion](https://doc.rust-lang.org/stable/std/primitive.tuple.html#trait-implementations-1)
674impl_try_from_robj_tuples!((1, 12));
675
676// The following is necessary because it is impossible to define `TryFrom<Robj> for &Robj` as
677// it requires returning a reference to a owned (moved) value
678
679impl TryFrom<&Robj> for HashMap<&str, Robj> {
680    type Error = Error;
681
682    fn try_from(value: &Robj) -> Result<Self> {
683        let value: List = value.try_into()?;
684        Ok(value.into_iter().collect())
685    }
686}
687
688impl TryFrom<&Robj> for HashMap<String, Robj> {
689    type Error = Error;
690    fn try_from(value: &Robj) -> Result<Self> {
691        let value: HashMap<&str, _> = value.try_into()?;
692        Ok(value.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
693    }
694}