Repository: mitsuhiko/minijinja (language: rust)

Issue:
Replace Arc<String> with Arc<str>
The current use of `Arc<String>` is pointless. All former optimizations of where `Arc::make_mut` was used are gone, and now we just carry the extra capacity around for no reason. Worse is that this also makes it inconvenient to handle as you cannot index into something like a `BTreeMap<Arc<String>, T>` without allocating a `Arc<String>` even just for lookups.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 64253f66..20edb64b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ All notable changes to MiniJinja are documented here.
 - `Value` now implements `Ord`.  This also improves the ability of the engine
   to sort more complex values in filters.  (#295)
 
+- `Arc<String>` was replaced with `Arc<str>` in some of the public APIs where
+  this shined through.  Support for more complex key types in maps was added.
+  You can now have tuple keys for instance.  (#297)
+
 ## 1.0.0-alpha.3
 
 - Removed `char` as a value type.  Characters are nor represented as strings
diff --git a/examples/dsl/src/main.rs b/examples/dsl/src/main.rs
index 758f10d0..7fabdc85 100644
--- a/examples/dsl/src/main.rs
+++ b/examples/dsl/src/main.rs
@@ -8,7 +8,7 @@ use minijinja::{Environment, Error, ErrorKind};
 /// A copy-on-write object that holds an assembled query.
 #[derive(Debug, Clone)]
 pub struct Query {
-    table: Arc<String>,
+    table: Arc<str>,
     filters: Arc<HashMap<String, Value>>,
     limit: Option<usize>,
     offset: Option<usize>,
@@ -24,7 +24,7 @@ impl Query {
     /// Creates an empty query object for a specific table.
     fn new(table: String) -> Self {
         Query {
-            table: Arc::new(table),
+            table: Arc::from(table),
             filters: Default::default(),
             limit: None,
             offset: None,
diff --git a/minijinja-py/src/typeconv.rs b/minijinja-py/src/typeconv.rs
index 94b95461..19530aa9 100644
--- a/minijinja-py/src/typeconv.rs
+++ b/minijinja-py/src/typeconv.rs
@@ -31,7 +31,7 @@ impl StructObject for DictLikeObject {
         })
     }
 
-    fn fields(&self) -> Vec<Arc<String>> {
+    fn fields(&self) -> Vec<Arc<str>> {
         Python::with_gil(|py| {
             let inner = self.inner.as_ref(py);
             inner.keys().iter().map(|x| x.to_string().into()).collect()
diff --git a/minijinja-stack-ref/src/lib.rs b/minijinja-stack-ref/src/lib.rs
index d2a1ab59..81ede5f3 100644
--- a/minijinja-stack-ref/src/lib.rs
+++ b/minijinja-stack-ref/src/lib.rs
@@ -338,7 +338,7 @@ impl<T: StructObject + Send + Sync + 'static + ?Sized> StructObject for StackHan
         self.with(|val| val.static_fields())
     }
 
-    fn fields(&self) -> Vec<Arc<String>> {
+    fn fields(&self) -> Vec<Arc<str>> {
         self.with(|val| val.fields())
     }
 
@@ -413,7 +413,7 @@ impl<T: Object + ?Sized> StructObject for StackHandleProxy<T> {
             .with(|val| unwrap_kind!(val, ObjectKind::Struct).get_field(name))
     }
 
-    fn fields(&self) -> Vec<Arc<String>> {
+    fn fields(&self) -> Vec<Arc<str>> {
         self.0
             .with(|val| unwrap_kind!(val, ObjectKind::Struct).fields())
     }
diff --git a/minijinja/src/compiler/ast.rs b/minijinja/src/compiler/ast.rs
index 69755920..483ad1c0 100644
--- a/minijinja/src/compiler/ast.rs
+++ b/minijinja/src/compiler/ast.rs
@@ -4,8 +4,7 @@ use std::ops::Deref;
 use std::fmt;
 
 use crate::compiler::tokens::Span;
-use crate::key::Key;
-use crate::value::{value_map_with_capacity, MapType, Value, ValueRepr};
+use crate::value::{value_map_with_capacity, KeyRef, MapType, Value, ValueRepr};
 
 /// Container for nodes with location info.
 ///
@@ -500,7 +499,7 @@ impl<'a> Kwargs<'a> {
         let mut rv = value_map_with_capacity(self.pairs.len());
         for (key, value) in &self.pairs {
             if let Expr::Const(value) = value {
-                rv.insert(Key::make_string_key(key), value.value.clone());
+                rv.insert(KeyRef::Value(Value::from(*key)), value.value.clone());
             }
         }
 
@@ -527,13 +526,7 @@ impl<'a> Map<'a> {
         let mut rv = value_map_with_capacity(self.keys.len());
         for (key, value) in self.keys.iter().zip(self.values.iter()) {
             if let (Expr::Const(maybe_key), Expr::Const(value)) = (key, value) {
-                rv.insert(
-                    match maybe_key.value.clone().try_into_key() {
-                        Ok(key) => key,
-                        Err(_) => return None,
-                    },
-                    value.value.clone(),
-                );
+                rv.insert(KeyRef::Value(maybe_key.value.clone()), value.value.clone());
             }
         }
 
diff --git a/minijinja/src/filters.rs b/minijinja/src/filters.rs
index 650c4870..81f30079 100644
--- a/minijinja/src/filters.rs
+++ b/minijinja/src/filters.rs
@@ -1205,13 +1205,9 @@ mod builtins {
         let mut seen = BTreeSet::new();
 
         for item in values {
-            if let Ok(key) = item.clone().try_into_key() {
-                if !seen.contains(&key) {
-                    seen.insert(key);
-                    rv.push(item.clone());
-                }
-            } else {
+            if !seen.contains(&item) {
                 rv.push(item.clone());
+                seen.insert(item);
             }
         }
 
diff --git a/minijinja/src/key/deserialize.rs b/minijinja/src/key/deserialize.rs
deleted file mode 100644
index b185f590..00000000
--- a/minijinja/src/key/deserialize.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use serde::de::{self, Visitor};
-use serde::Deserialize;
-
-use crate::key::{Key, StaticKey};
-
-impl<'de> Deserialize<'de> for StaticKey {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let visitor = KeyVisitor;
-        deserializer.deserialize_any(visitor)
-    }
-}
-
-struct KeyVisitor;
-
-macro_rules! visit_key_primitive {
-    ($name:ident, $ty:ty, $enum_ty:ident) => {
-        fn $name<E>(self, v: $ty) -> Result<Self::Value, E>
-        where
-            E: serde::de::Error,
-        {
-            Ok(Key::$enum_ty(v as _))
-        }
-    };
-}
-
-impl<'de> Visitor<'de> for KeyVisitor {
-    type Value = StaticKey;
-
-    fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
-        fmt.write_str("any MiniJinja compatible value")
-    }
-
-    visit_key_primitive!(visit_bool, bool, Bool);
-    visit_key_primitive!(visit_i8, i8, I64);
-    visit_key_primitive!(visit_i16, i16, I64);
-    visit_key_primitive!(visit_i32, i32, I64);
-    visit_key_primitive!(visit_i64, i64, I64);
-    visit_key_primitive!(visit_u8, u8, I64);
-    visit_key_primitive!(visit_u16, u16, I64);
-    visit_key_primitive!(visit_u32, u32, I64);
-    visit_key_primitive!(visit_u64, u64, I64);
-
-    fn visit_char<E>(self, v: char) -> Result<Self::Value, E>
-    where
-        E: de::Error,
-    {
-        Ok(Key::from(v.to_string()))
-    }
-
-    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
-    where
-        E: de::Error,
-    {
-        Ok(Key::make_string_key(v))
-    }
-
-    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        Deserialize::deserialize(deserializer)
-    }
-
-    fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        Deserialize::deserialize(deserializer)
-    }
-}
diff --git a/minijinja/src/key/mod.rs b/minijinja/src/key/mod.rs
deleted file mode 100644
index ec6c6a53..00000000
--- a/minijinja/src/key/mod.rs
+++ /dev/null
@@ -1,326 +0,0 @@
-use std::cmp::Ordering;
-use std::convert::TryFrom;
-use std::fmt;
-use std::hash::{Hash, Hasher};
-use std::num::TryFromIntError;
-use std::sync::Arc;
-
-use crate::error::{Error, ErrorKind};
-use crate::value::{Value, ValueRepr};
-
-pub use crate::key::serialize::KeySerializer;
-
-#[cfg(feature = "deserialization")]
-mod deserialize;
-mod serialize;
-
-/// Represents a key in a value's map.
-#[derive(Clone)]
-pub enum Key<'a> {
-    Bool(bool),
-    I64(i64),
-    String(Arc<String>),
-    Str(&'a str),
-}
-
-pub type StaticKey = Key<'static>;
-
-impl<'a> fmt::Debug for Key<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::Bool(val) => fmt::Debug::fmt(val, f),
-            Self::I64(val) => fmt::Debug::fmt(val, f),
-            Self::String(val) => fmt::Debug::fmt(val, f),
-            Self::Str(val) => fmt::Debug::fmt(val, f),
-        }
-    }
-}
-
-#[derive(PartialOrd, Ord, Eq, PartialEq, Hash)]
-enum InternalKeyRef<'a> {
-    Bool(bool),
-    I64(i64),
-    Str(&'a str),
-}
-
-impl<'a> Key<'a> {
-    pub fn make_string_key(s: &str) -> StaticKey {
-        #[cfg(feature = "key_interning")]
-        {
-            Key::String(key_interning::try_intern(s))
-        }
-        #[cfg(not(feature = "key_interning"))]
-        {
-            Key::String(Arc::new(String::from(s)))
-        }
-    }
-
-    pub fn as_str(&self) -> Option<&str> {
-        match *self {
-            Key::String(ref x) => Some(x.as_str()),
-            Key::Str(x) => Some(x),
-            _ => None,
-        }
-    }
-
-    fn as_key_ref(&self) -> InternalKeyRef<'_> {
-        match *self {
-            Key::Bool(x) => InternalKeyRef::Bool(x),
-            Key::I64(x) => InternalKeyRef::I64(x),
-            Key::String(ref x) => InternalKeyRef::Str(x.as_str()),
-            Key::Str(x) => InternalKeyRef::Str(x),
-        }
-    }
-
-    pub fn from_borrowed_value(value: &'a Value) -> Result<Key<'a>, Error> {
-        match value.0 {
-            ValueRepr::Bool(v) => Ok(Key::Bool(v)),
-            ValueRepr::U64(v) => TryFrom::try_from(v)
-                .map(Key::I64)
-                .map_err(|_| ErrorKind::NonKey.into()),
-            ValueRepr::U128(v) => TryFrom::try_from(v.0)
-                .map(Key::I64)
-                .map_err(|_| ErrorKind::NonKey.into()),
-            ValueRepr::I64(v) => Ok(Key::I64(v)),
-            ValueRepr::I128(v) => TryFrom::try_from(v.0)
-                .map(Key::I64)
-                .map_err(|_| ErrorKind::NonKey.into()),
-            ValueRepr::F64(x) => {
-                // if a float is in fact looking like an integer we
-                // allow this to be used for indexing.  Why?  Because
-                // in Jinja division is always a division resulting
-                // in floating point values (4 / 2 == 2.0).
-                let intval = x as i64;
-                if intval as f64 == x {
-                    Ok(Key::I64(intval))
-                } else {
-                    Err(ErrorKind::NonKey.into())
-                }
-            }
-            ValueRepr::String(ref s, _) => Ok(Key::Str(s)),
-            _ => Err(ErrorKind::NonKey.into()),
-        }
-    }
-}
-
-impl<'a> PartialEq for Key<'a> {
-    #[inline(always)]
-    fn eq(&self, other: &Self) -> bool {
-        self.as_key_ref().eq(&other.as_key_ref())
-    }
-}
-
-impl<'a> Eq for Key<'a> {}
-
-impl<'a> Hash for Key<'a> {
-    #[inline(always)]
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.as_key_ref().hash(state)
-    }
-}
-
-impl<'a> PartialOrd for Key<'a> {
-    #[inline(always)]
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.as_key_ref().partial_cmp(&other.as_key_ref())
-    }
-}
-
-impl<'a> Ord for Key<'a> {
-    #[inline(always)]
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.as_key_ref().cmp(&other.as_key_ref())
-    }
-}
-
-impl<'a> fmt::Display for Key<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Key::Bool(val) => val.fmt(f),
-            Key::I64(val) => val.fmt(f),
-            Key::String(val) => val.fmt(f),
-            Key::Str(val) => val.fmt(f),
-        }
-    }
-}
-
-macro_rules! key_from {
-    ($src:ty, $dst:ident) => {
-        impl From<$src> for StaticKey {
-            #[inline(always)]
-            fn from(val: $src) -> Self {
-                Key::$dst(val as _)
-            }
-        }
-    };
-}
-
-key_from!(bool, Bool);
-key_from!(u8, I64);
-key_from!(u16, I64);
-key_from!(u32, I64);
-key_from!(i8, I64);
-key_from!(i16, I64);
-key_from!(i32, I64);
-key_from!(i64, I64);
-
-impl TryFrom<u64> for StaticKey {
-    type Error = TryFromIntError;
-
-    fn try_from(value: u64) -> Result<Self, Self::Error> {
-        TryFrom::try_from(value).map(Key::I64)
-    }
-}
-
-impl From<Arc<String>> for StaticKey {
-    #[inline(always)]
-    fn from(value: Arc<String>) -> Self {
-        Key::String(value)
-    }
-}
-
-impl From<String> for StaticKey {
-    #[inline(always)]
-    fn from(value: String) -> Self {
-        Key::String(Arc::new(value))
-    }
-}
-
-impl<'a> From<&'a str> for StaticKey {
-    #[inline(always)]
-    fn from(value: &'a str) -> Self {
-        Key::make_string_key(value)
-    }
-}
-
-#[cfg(feature = "key_interning")]
-pub mod key_interning {
-    use crate::utils::OnDrop;
-
-    use super::*;
-    use std::cell::{Cell, RefCell};
-    use std::collections::HashSet;
-
-    thread_local! {
-        static STRING_KEY_CACHE: RefCell<HashSet<CachedKey<'static>>> = Default::default();
-        static USE_STRING_KEY_CACHE: Cell<bool> = Cell::new(false);
-    }
-
-    enum CachedKey<'a> {
-        Ref(&'a str),
-        Stored(Arc<String>),
-    }
-
-    impl<'a> CachedKey<'a> {
-        fn as_str(&self) -> &str {
-            match self {
-                CachedKey::Ref(x) => x,
-                CachedKey::Stored(x) => x.as_str(),
-            }
-        }
-    }
-
-    impl<'a> Hash for CachedKey<'a> {
-        fn hash<H: Hasher>(&self, state: &mut H) {
-            self.as_str().hash(state)
-        }
-    }
-
-    impl<'a> PartialEq for CachedKey<'a> {
-        fn eq(&self, other: &Self) -> bool {
-            self.as_str().eq(other.as_str())
-        }
-    }
-
-    impl<'a> Eq for CachedKey<'a> {}
-
-    pub fn use_string_cache() -> impl Drop {
-        let was_enabled = USE_STRING_KEY_CACHE.with(|flag| {
-            let was_enabled = flag.get();
-            flag.set(true);
-            was_enabled
-        });
-        OnDrop::new(move || {
-            if !was_enabled {
-                USE_STRING_KEY_CACHE.with(|flag| flag.set(false));
-                STRING_KEY_CACHE.with(|cache| cache.borrow_mut().clear());
-            }
-        })
-    }
-
-    #[inline(always)]
-    pub(crate) fn try_intern(s: &str) -> Arc<String> {
-        // strings longer than 16 bytes are never interned or if we're at
-        // depth 0.  (serialization code outside of internal serialization)
-        // not checking for depth can cause a memory leak.
-        if s.len() > 16 || !USE_STRING_KEY_CACHE.with(|flag| flag.get()) {
-            return Arc::new(String::from(s));
-        }
-
-        STRING_KEY_CACHE.with(|cache| {
-            let mut set = cache.borrow_mut();
-            match set.get(&CachedKey::Ref(s)) {
-                Some(CachedKey::Stored(s)) => s.clone(),
-                None => {
-                    let rv = Arc::new(String::from(s));
-                    set.insert(CachedKey::Stored(rv.clone()));
-                    rv
-                }
-                _ => unreachable!(),
-            }
-        })
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    use similar_asserts::assert_eq;
-
-    #[test]
-    fn test_string_key_lookup() {
-        let mut m = std::collections::BTreeMap::new();
-        m.insert(Key::String(Arc::new("foo".into())), Value::from(42));
-        let m = Value::from(m);
-        assert_eq!(m.get_item(&Value::from("foo")).unwrap(), Value::from(42));
-    }
-
-    #[test]
-    fn test_int_key_lookup() {
-        let mut m = std::collections::BTreeMap::new();
-        m.insert(Key::I64(42), Value::from(42));
-        m.insert(Key::I64(23), Value::from(23));
-        let m = Value::from(m);
-        assert_eq!(m.get_item(&Value::from(42.0f32)).unwrap(), Value::from(42));
-        assert_eq!(m.get_item(&Value::from(42u32)).unwrap(), Value::from(42));
-
-        let s = Value::from(vec![42i32, 23]);
-        assert_eq!(s.get_item(&Value::from(0.0f32)).unwrap(), Value::from(42));
-        assert_eq!(s.get_item(&Value::from(0i32)).unwrap(), Value::from(42));
-    }
-
-    #[cfg(feature = "key_interning")]
-    #[test]
-    fn test_key_interning() {
-        let mut m = std::collections::BTreeMap::new();
-        m.insert("x", 1u32);
-
-        let v = Value::from_serializable(&vec![m.clone(), m.clone(), m.clone()]);
-
-        for value in v.try_iter_owned().unwrap() {
-            match value.0 {
-                ValueRepr::Map(m, _) => {
-                    let k = m.iter().next().unwrap().0;
-                    match k {
-                        Key::String(s) => {
-                            assert_eq!(Arc::strong_count(s), 3);
-                        }
-                        _ => unreachable!(),
-                    }
-                }
-                _ => unreachable!(),
-            }
-        }
-    }
-}
diff --git a/minijinja/src/key/serialize.rs b/minijinja/src/key/serialize.rs
deleted file mode 100644
index 95141555..00000000
--- a/minijinja/src/key/serialize.rs
+++ /dev/null
@@ -1,241 +0,0 @@
-use std::convert::TryFrom;
-use std::fmt;
-
-use serde::ser::{Impossible, Serialize, Serializer};
-
-use crate::key::{Key, StaticKey};
-
-/// Value is not a valid key.
-#[derive(Debug)]
-pub struct InvalidKey;
-
-impl std::error::Error for InvalidKey {}
-
-impl fmt::Display for InvalidKey {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_str("invalid key")
-    }
-}
-
-impl serde::ser::Error for InvalidKey {
-    #[track_caller]
-    #[cold]
-    fn custom<T>(msg: T) -> Self
-    where
-        T: fmt::Display,
-    {
-        let _ = msg;
-        InvalidKey
-    }
-}
-
-impl<'a> Serialize for Key<'a> {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        match *self {
-            Key::Bool(b) => serializer.serialize_bool(b),
-            Key::I64(i) => serializer.serialize_i64(i),
-            Key::String(ref s) => serializer.serialize_str(s),
-            Key::Str(s) => serializer.serialize_str(s),
-        }
-    }
-}
-
-pub struct KeySerializer;
-
-impl Serializer for KeySerializer {
-    type Ok = StaticKey;
-    type Error = InvalidKey;
-
-    type SerializeSeq = Impossible<StaticKey, InvalidKey>;
-    type SerializeTuple = Impossible<StaticKey, InvalidKey>;
-    type SerializeTupleStruct = Impossible<StaticKey, InvalidKey>;
-    type SerializeTupleVariant = Impossible<StaticKey, InvalidKey>;
-    type SerializeMap = Impossible<StaticKey, InvalidKey>;
-    type SerializeStruct = Impossible<StaticKey, InvalidKey>;
-    type SerializeStructVariant = Impossible<StaticKey, InvalidKey>;
-
-    fn serialize_bool(self, v: bool) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::Bool(v))
-    }
-
-    fn serialize_i8(self, v: i8) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::I64(v as i64))
-    }
-
-    fn serialize_i16(self, v: i16) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::I64(v as i64))
-    }
-
-    fn serialize_i32(self, v: i32) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::I64(v as i64))
-    }
-
-    fn serialize_i64(self, v: i64) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::I64(v))
-    }
-
-    #[cold]
-    fn serialize_i128(self, _: i128) -> Result<StaticKey, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    fn serialize_u8(self, v: u8) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::I64(v as i64))
-    }
-
-    fn serialize_u16(self, v: u16) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::I64(v as i64))
-    }
-
-    fn serialize_u32(self, v: u32) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::I64(v as i64))
-    }
-
-    fn serialize_u64(self, v: u64) -> Result<StaticKey, InvalidKey> {
-        match Key::try_from(v) {
-            Ok(rv) => Ok(rv),
-            Err(_) => Err(InvalidKey),
-        }
-    }
-
-    #[cold]
-    fn serialize_u128(self, _: u128) -> Result<StaticKey, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_f32(self, _: f32) -> Result<StaticKey, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_f64(self, _: f64) -> Result<StaticKey, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    fn serialize_char(self, v: char) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::from(v.to_string()))
-    }
-
-    fn serialize_str(self, value: &str) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::make_string_key(value))
-    }
-
-    #[cold]
-    fn serialize_bytes(self, _value: &[u8]) -> Result<StaticKey, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_none(self) -> Result<StaticKey, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    fn serialize_some<T: ?Sized>(self, value: &T) -> Result<StaticKey, InvalidKey>
-    where
-        T: Serialize,
-    {
-        value.serialize(self)
-    }
-
-    #[cold]
-    fn serialize_unit(self) -> Result<StaticKey, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_unit_struct(self, _name: &'static str) -> Result<StaticKey, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    fn serialize_unit_variant(
-        self,
-        _name: &'static str,
-        _variant_index: u32,
-        variant: &'static str,
-    ) -> Result<StaticKey, InvalidKey> {
-        Ok(Key::Str(variant))
-    }
-
-    fn serialize_newtype_struct<T: ?Sized>(
-        self,
-        _name: &'static str,
-        value: &T,
-    ) -> Result<StaticKey, InvalidKey>
-    where
-        T: Serialize,
-    {
-        value.serialize(self)
-    }
-
-    fn serialize_newtype_variant<T: ?Sized>(
-        self,
-        _name: &'static str,
-        _variant_index: u32,
-        _variant: &'static str,
-        value: &T,
-    ) -> Result<StaticKey, InvalidKey>
-    where
-        T: Serialize,
-    {
-        value.serialize(self)
-    }
-
-    #[cold]
-    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_tuple_struct(
-        self,
-        _name: &'static str,
-        _len: usize,
-    ) -> Result<Self::SerializeTupleStruct, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_tuple_variant(
-        self,
-        _name: &'static str,
-        _variant_index: u32,
-        _variant: &'static str,
-        _len: usize,
-    ) -> Result<Self::SerializeTupleVariant, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_map(self, _jlen: Option<usize>) -> Result<Self::SerializeMap, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_struct(
-        self,
-        _name: &'static str,
-        _len: usize,
-    ) -> Result<Self::SerializeStruct, InvalidKey> {
-        Err(InvalidKey)
-    }
-
-    #[cold]
-    fn serialize_struct_variant(
-        self,
-        _name: &'static str,
-        _variant_index: u32,
-        _variant: &'static str,
-        _len: usize,
-    ) -> Result<Self::SerializeStructVariant, InvalidKey> {
-        Err(InvalidKey)
-    }
-}
diff --git a/minijinja/src/lib.rs b/minijinja/src/lib.rs
index cd8f9abd..d3ef5701 100644
--- a/minijinja/src/lib.rs
+++ b/minijinja/src/lib.rs
@@ -194,7 +194,6 @@ mod defaults;
 mod environment;
 mod error;
 mod expression;
-mod key;
 mod output;
 mod template;
 mod utils;
diff --git a/minijinja/src/macros.rs b/minijinja/src/macros.rs
index 94580a88..c2a825f3 100644
--- a/minijinja/src/macros.rs
+++ b/minijinja/src/macros.rs
@@ -23,8 +23,7 @@ macro_rules! some {
 /// Hidden utility module for the [`context!`](crate::context!) macro.
 #[doc(hidden)]
 pub mod __context {
-    use crate::key::Key;
-    use crate::value::{MapType, Value, ValueMap, ValueRepr};
+    use crate::value::{KeyRef, MapType, Value, ValueMap, ValueRepr};
     use crate::Environment;
     use std::rc::Rc;
     use std::sync::Arc;
@@ -41,7 +40,7 @@ pub mod __context {
 
     #[inline(always)]
     pub fn add(ctx: &mut ValueMap, key: &'static str, value: Value) {
-        ctx.insert(Key::Str(key), value);
+        ctx.insert(KeyRef::Str(key), value);
     }
 
     #[inline(always)]
diff --git a/minijinja/src/utils.rs b/minijinja/src/utils.rs
index 76a83ed1..63ee909b 100644
--- a/minijinja/src/utils.rs
+++ b/minijinja/src/utils.rs
@@ -21,6 +21,12 @@ pub fn memstr(haystack: &[u8], needle: &[u8]) -> Option<usize> {
         .position(|window| window == needle)
 }
 
+/// Helper for dealing with untrusted size hints.
+#[inline(always)]
+pub(crate) fn untrusted_size_hint(value: usize) -> usize {
+    value.min(1024)
+}
+
 fn write_with_html_escaping(out: &mut Output, value: &Value) -> fmt::Result {
     if matches!(
         value.kind(),
diff --git a/minijinja/src/value/argtypes.rs b/minijinja/src/value/argtypes.rs
index 1637086c..72afd4a3 100644
--- a/minijinja/src/value/argtypes.rs
+++ b/minijinja/src/value/argtypes.rs
@@ -5,10 +5,10 @@ use std::convert::TryFrom;
 use std::ops::{Deref, DerefMut};
 
 use crate::error::{Error, ErrorKind};
-use crate::key::{Key, StaticKey};
 use crate::utils::UndefinedBehavior;
 use crate::value::{
-    Arc, MapType, Object, Packed, SeqObject, StringType, Value, ValueKind, ValueMap, ValueRepr,
+    Arc, KeyRef, MapType, Object, Packed, SeqObject, StringType, Value, ValueKind, ValueMap,
+    ValueRepr,
 };
 use crate::vm::State;
 
@@ -265,14 +265,14 @@ impl<'a> From<&'a [u8]> for Value {
 impl<'a> From<&'a str> for Value {
     #[inline(always)]
     fn from(val: &'a str) -> Self {
-        ValueRepr::String(Arc::new(val.into()), StringType::Normal).into()
+        ValueRepr::String(Arc::from(val.to_string()), StringType::Normal).into()
     }
 }
 
 impl From<String> for Value {
     #[inline(always)]
     fn from(val: String) -> Self {
-        ValueRepr::String(Arc::new(val), StringType::Normal).into()
+        ValueRepr::String(Arc::from(val), StringType::Normal).into()
     }
 }
 
@@ -293,17 +293,6 @@ impl From<()> for Value {
     }
 }
 
-impl<'a> From<Key<'a>> for Value {
-    fn from(val: Key) -> Self {
-        match val {
-            Key::Bool(val) => val.into(),
-            Key::I64(val) => val.into(),
-            Key::String(val) => ValueRepr::String(val, StringType::Normal).into(),
-            Key::Str(val) => val.into(),
-        }
-    }
-}
-
 impl<V: Into<Value>> FromIterator<V> for Value {
     fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
         let vec = iter.into_iter().map(|v| v.into()).collect();
@@ -312,24 +301,26 @@ impl<V: Into<Value>> FromIterator<V> for Value {
     }
 }
 
-impl<K: Into<StaticKey>, V: Into<Value>> FromIterator<(K, V)> for Value {
+impl<K: Into<Value>, V: Into<Value>> FromIterator<(K, V)> for Value {
     fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
         let map = iter
             .into_iter()
-            .map(|(k, v)| (k.into(), v.into()))
+            .map(|(k, v)| (KeyRef::Value(k.into()), v.into()))
             .collect();
 
         ValueRepr::Map(Arc::new(map), MapType::Normal).into()
     }
 }
 
-impl<K: Into<StaticKey>, V: Into<Value>> From<BTreeMap<K, V>> for Value {
+impl<K: Into<Value>, V: Into<Value>> From<BTreeMap<K, V>> for Value {
     fn from(val: BTreeMap<K, V>) -> Self {
-        val.into_iter().map(|(k, v)| (k.into(), v.into())).collect()
+        val.into_iter()
+            .map(|(k, v)| (KeyRef::Value(k.into()), v.into()))
+            .collect()
     }
 }
 
-impl<K: Into<StaticKey>, V: Into<Value>> From<HashMap<K, V>> for Value {
+impl<K: Into<Value>, V: Into<Value>> From<HashMap<K, V>> for Value {
     fn from(val: HashMap<K, V>) -> Self {
         val.into_iter().map(|(k, v)| (k.into(), v.into())).collect()
     }
@@ -347,8 +338,8 @@ impl<T: Object> From<Arc<T>> for Value {
     }
 }
 
-impl From<Arc<String>> for Value {
-    fn from(value: Arc<String>) -> Self {
+impl From<Arc<str>> for Value {
+    fn from(value: Arc<str>) -> Self {
         Value(ValueRepr::String(value, StringType::Normal))
     }
 }
@@ -381,7 +372,7 @@ impl From<u128> for Value {
 impl From<char> for Value {
     #[inline(always)]
     fn from(val: char) -> Self {
-        ValueRepr::String(Arc::new(val.to_string()), StringType::Normal).into()
+        ValueRepr::String(Arc::from(val.to_string()), StringType::Normal).into()
     }
 }
 
@@ -561,7 +552,7 @@ impl<'a> ArgType<'a> for Cow<'_, str> {
     fn from_value(value: Option<&'a Value>) -> Result<Cow<'a, str>, Error> {
         match value {
             Some(value) => Ok(match value.0 {
-                ValueRepr::String(ref s, _) => Cow::Borrowed(s.as_str()),
+                ValueRepr::String(ref s, _) => Cow::Borrowed(s as &str),
                 _ => Cow::Owned(value.to_string()),
             }),
             None => Err(Error::from(ErrorKind::MissingArgument)),
@@ -772,7 +763,7 @@ impl Kwargs {
     where
         T: ArgType<'a, Output = T>,
     {
-        T::from_value(self.values.get(&Key::Str(key)))
+        T::from_value(self.values.get(&KeyRef::Str(key)))
     }
 
     /// Gets a single argument from the kwargs and marks it as used.
@@ -807,7 +798,7 @@ impl Kwargs {
 
     /// Checks if a keyword argument exists.
     pub fn has(&self, key: &str) -> bool {
-        self.values.contains_key(&Key::Str(key))
+        self.values.contains_key(&KeyRef::Str(key))
     }
 
     /// Iterates over all passed keyword arguments.
@@ -843,7 +834,9 @@ impl FromIterator<(String, Value)> for Kwargs {
         T: IntoIterator<Item = (String, Value)>,
     {
         Kwargs::new(Arc::new(
-            iter.into_iter().map(|(k, v)| (Key::from(k), v)).collect(),
+            iter.into_iter()
+                .map(|(k, v)| (KeyRef::Value(Value::from(k)), v))
+                .collect(),
         ))
     }
 }
@@ -854,7 +847,9 @@ impl<'a> FromIterator<(&'a str, Value)> for Kwargs {
         T: IntoIterator<Item = (&'a str, Value)>,
     {
         Kwargs::new(Arc::new(
-            iter.into_iter().map(|(k, v)| (Key::from(k), v)).collect(),
+            iter.into_iter()
+                .map(|(k, v)| (KeyRef::Value(Value::from(k)), v))
+                .collect(),
         ))
     }
 }
diff --git a/minijinja/src/value/deserialize.rs b/minijinja/src/value/deserialize.rs
index d5cc5364..65acc116 100644
--- a/minijinja/src/value/deserialize.rs
+++ b/minijinja/src/value/deserialize.rs
@@ -1,7 +1,7 @@
 use serde::de::{self, MapAccess, SeqAccess, Visitor};
 use serde::Deserialize;
 
-use crate::value::{MapType, Value, ValueMap, ValueRepr};
+use crate::value::{KeyRef, MapType, Value, ValueMap, ValueRepr};
 
 impl<'de> Deserialize<'de> for Value {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@@ -96,7 +96,7 @@ impl<'de> Visitor<'de> for ValueVisitor {
     {
         let mut rv = ValueMap::default();
         while let Some((k, v)) = ok!(map.next_entry()) {
-            rv.insert(k, v);
+            rv.insert(KeyRef::Value(k), v);
         }
         Ok(Value(ValueRepr::Map(rv.into(), MapType::Normal)))
     }
diff --git a/minijinja/src/value/keyref.rs b/minijinja/src/value/keyref.rs
new file mode 100644
index 00000000..b31c092a
--- /dev/null
+++ b/minijinja/src/value/keyref.rs
@@ -0,0 +1,175 @@
+use std::cmp::Ordering;
+use std::fmt;
+use std::hash::{Hash, Hasher};
+
+use serde::Serialize;
+
+use crate::value::{intern, Value};
+
+/// Internal abstraction over keys
+#[derive(Clone)]
+pub enum KeyRef<'a> {
+    /// The key is a value
+    Value(Value),
+    /// The key is a string slice
+    Str(&'a str),
+}
+
+impl<'a> KeyRef<'a> {
+    /// If this is a str, return it.
+    pub fn as_str(&self) -> Option<&str> {
+        match self {
+            KeyRef::Value(v) => v.as_str(),
+            KeyRef::Str(s) => Some(s),
+        }
+    }
+
+    /// If this is an i64 return it
+    pub fn as_i64(&self) -> Option<i64> {
+        match self {
+            KeyRef::Value(v) => i64::try_from(v.clone()).ok(),
+            KeyRef::Str(_) => None,
+        }
+    }
+}
+
+impl<'a> From<&'a Value> for KeyRef<'a> {
+    fn from(value: &'a Value) -> Self {
+        if let Some(s) = value.as_str() {
+            KeyRef::Str(s)
+        } else {
+            KeyRef::Value(value.clone())
+        }
+    }
+}
+
+impl<'a> From<KeyRef<'a>> for Value {
+    fn from(value: KeyRef<'a>) -> Self {
+        match value {
+            KeyRef::Value(v) => v,
+            KeyRef::Str(s) => Value::from(intern(s)),
+        }
+    }
+}
+
+impl<'a> From<&'a KeyRef<'a>> for Value {
+    fn from(value: &'a KeyRef<'a>) -> Self {
+        match value {
+            KeyRef::Value(v) => v.clone(),
+            KeyRef::Str(s) => Value::from(intern(s)),
+        }
+    }
+}
+
+impl<'a> Serialize for KeyRef<'a> {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        match self {
+            KeyRef::Value(v) => v.serialize(serializer),
+            KeyRef::Str(s) => s.serialize(serializer),
+        }
+    }
+}
+
+impl<'a> fmt::Debug for KeyRef<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Value(v) => fmt::Debug::fmt(v, f),
+            Self::Str(v) => fmt::Debug::fmt(v, f),
+        }
+    }
+}
+
+impl<'a> PartialEq for KeyRef<'a> {
+    fn eq(&self, other: &Self) -> bool {
+        if let (Some(a), Some(b)) = (self.as_str(), other.as_str()) {
+            a.eq(b)
+        } else {
+            Value::from(self).eq(&Value::from(other))
+        }
+    }
+}
+
+impl<'a> Eq for KeyRef<'a> {}
+
+impl<'a> PartialOrd for KeyRef<'a> {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        if let (Some(a), Some(b)) = (self.as_str(), other.as_str()) {
+            a.partial_cmp(b)
+        } else {
+            Value::from(self).partial_cmp(&Value::from(other))
+        }
+    }
+}
+
+impl<'a> Ord for KeyRef<'a> {
+    fn cmp(&self, other: &Self) -> Ordering {
+        if let (Some(a), Some(b)) = (self.as_str(), other.as_str()) {
+            a.cmp(b)
+        } else {
+            Value::from(self).cmp(&Value::from(other))
+        }
+    }
+}
+
+impl<'a> Hash for KeyRef<'a> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        if let Some(s) = self.as_str() {
+            s.hash(state)
+        } else {
+            Value::from(self).hash(state)
+        }
+    }
+}
+
+#[cfg(feature = "key_interning")]
+pub mod key_interning {
+    use crate::utils::OnDrop;
+
+    use std::cell::{Cell, RefCell};
+    use std::collections::HashSet;
+    use std::sync::Arc;
+
+    thread_local! {
+        static STRING_KEY_CACHE: RefCell<HashSet<Arc<str>>> = Default::default();
+        static USE_STRING_KEY_CACHE: Cell<bool> = Cell::new(false);
+    }
+
+    pub fn use_string_cache() -> impl Drop {
+        let was_enabled = USE_STRING_KEY_CACHE.with(|flag| {
+            let was_enabled = flag.get();
+            flag.set(true);
+            was_enabled
+        });
+        OnDrop::new(move || {
+            if !was_enabled {
+                USE_STRING_KEY_CACHE.with(|flag| flag.set(false));
+                STRING_KEY_CACHE.with(|cache| cache.borrow_mut().clear());
+            }
+        })
+    }
+
+    #[inline(always)]
+    pub(crate) fn try_intern(s: &str) -> Arc<str> {
+        // strings longer than 16 bytes are never interned or if we're at
+        // depth 0.  (serialization code outside of internal serialization)
+        // not checking for depth can cause a memory leak.
+        if s.len() > 16 || !USE_STRING_KEY_CACHE.with(|flag| flag.get()) {
+            return Arc::from(s);
+        }
+
+        STRING_KEY_CACHE.with(|cache| {
+            let mut set = cache.borrow_mut();
+            match set.get(s) {
+                Some(stored) => stored.clone(),
+                None => {
+                    let rv: Arc<str> = Arc::from(s.to_string());
+                    set.insert(rv.clone());
+                    rv
+                }
+            }
+        })
+    }
+}
diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs
index 77627380..9b011de5 100644
--- a/minijinja/src/value/mod.rs
+++ b/minijinja/src/value/mod.rs
@@ -106,6 +106,7 @@ use std::cmp::Ordering;
 use std::collections::BTreeMap;
 use std::convert::TryFrom;
 use std::fmt;
+use std::hash::{Hash, Hasher};
 use std::marker::PhantomData;
 use std::sync::Arc;
 
@@ -113,9 +114,9 @@ use serde::ser::{Serialize, Serializer};
 
 use crate::error::{Error, ErrorKind};
 use crate::functions;
-use crate::key::{Key, StaticKey};
 use crate::utils::OnDrop;
 use crate::value::object::{SimpleSeqObject, SimpleStructObject};
+use crate::value::ops::as_f64;
 use crate::value::serialize::transform;
 use crate::vm::State;
 
@@ -125,19 +126,22 @@ pub use crate::value::object::{Object, ObjectKind, SeqObject, SeqObjectIter, Str
 mod argtypes;
 #[cfg(feature = "deserialization")]
 mod deserialize;
+mod keyref;
 mod object;
 pub(crate) mod ops;
 mod serialize;
 
+pub(crate) use crate::value::keyref::KeyRef;
+
 // We use in-band signalling to roundtrip some internal values.  This is
 // not ideal but unfortunately there is no better system in serde today.
 const VALUE_HANDLE_MARKER: &str = "\x01__minijinja_ValueHandle";
 
 #[cfg(feature = "preserve_order")]
-pub(crate) type ValueMap = indexmap::IndexMap<StaticKey, Value>;
+pub(crate) type ValueMap = indexmap::IndexMap<KeyRef<'static>, Value>;
 
 #[cfg(not(feature = "preserve_order"))]
-pub(crate) type ValueMap = std::collections::BTreeMap<StaticKey, Value>;
+pub(crate) type ValueMap = std::collections::BTreeMap<KeyRef<'static>, Value>;
 
 #[inline(always)]
 pub(crate) fn value_map_with_capacity(capacity: usize) -> ValueMap {
@@ -148,7 +152,7 @@ pub(crate) fn value_map_with_capacity(capacity: usize) -> ValueMap {
     }
     #[cfg(feature = "preserve_order")]
     {
-        ValueMap::with_capacity(capacity)
+        ValueMap::with_capacity(crate::utils::untrusted_size_hint(capacity))
     }
 }
 
@@ -181,16 +185,20 @@ pub fn serializing_for_value() -> bool {
     INTERNAL_SERIALIZATION.with(|flag| flag.get())
 }
 
-/// When key interning is enabled while the returned token is held keys are
-/// interned in a map, freed at the end of the section.
-#[cfg(feature = "key_interning")]
-pub(crate) use crate::key::key_interning::use_string_cache as value_optimization;
-
-/// Without key interning this is a dummy drop.
-#[cfg(not(feature = "key_interning"))]
+/// Enables value optimizations.
+///
+/// If `key_interning` is enabled, this turns on that feature, otherwise
+/// it becomes a noop.
 #[inline(always)]
 pub(crate) fn value_optimization() -> impl Drop {
-    OnDrop::new(|| {})
+    #[cfg(feature = "key_interning")]
+    {
+        crate::value::keyref::key_interning::use_string_cache()
+    }
+    #[cfg(not(feature = "key_interning"))]
+    {
+        OnDrop::new(|| {})
+    }
 }
 
 fn mark_internal_serialization() -> impl Drop {
@@ -283,10 +291,10 @@ pub(crate) enum ValueRepr {
     I64(i64),
     F64(f64),
     None,
-    Invalid(Arc<String>),
+    Invalid(Arc<str>),
     U128(Packed<u128>),
     I128(Packed<i128>),
-    String(Arc<String>, StringType),
+    String(Arc<str>, StringType),
     Bytes(Arc<Vec<u8>>),
     Seq(Arc<Vec<Value>>),
     Map(Arc<ValueMap>, MapType),
@@ -314,6 +322,51 @@ impl fmt::Debug for ValueRepr {
     }
 }
 
+impl Hash for Value {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        match &self.0 {
+            ValueRepr::None | ValueRepr::Undefined => 0u8.hash(state),
+            ValueRepr::String(ref s, _) => s.hash(state),
+            ValueRepr::Bool(b) => b.hash(state),
+            ValueRepr::Invalid(s) => s.hash(state),
+            ValueRepr::Bytes(b) => b.hash(state),
+            ValueRepr::Seq(b) => b.hash(state),
+            ValueRepr::Map(m, _) => m.iter().for_each(|(k, v)| {
+                k.hash(state);
+                v.hash(state);
+            }),
+            ValueRepr::Dynamic(d) => match d.kind() {
+                ObjectKind::Plain => 0u8.hash(state),
+                ObjectKind::Seq(s) => s.iter().for_each(|x| x.hash(state)),
+                ObjectKind::Struct(s) => {
+                    if let Some(fields) = s.static_fields() {
+                        fields.iter().for_each(|k| {
+                            k.hash(state);
+                            s.get_field(k).hash(state);
+                        });
+                    } else {
+                        s.fields().iter().for_each(|k| {
+                            k.hash(state);
+                            s.get_field(k).hash(state);
+                        });
+                    }
+                }
+            },
+            ValueRepr::U64(_)
+            | ValueRepr::I64(_)
+            | ValueRepr::F64(_)
+            | ValueRepr::U128(_)
+            | ValueRepr::I128(_) => {
+                if let Ok(val) = i64::try_from(self.clone()) {
+                    val.hash(state)
+                } else {
+                    as_f64(self).map(|x| x.to_bits()).hash(state)
+                }
+            }
+        }
+    }
+}
+
 /// Represents a dynamically typed value in the template engine.
 #[derive(Clone)]
 pub struct Value(pub(crate) ValueRepr);
@@ -472,11 +525,14 @@ impl Default for Value {
 /// same functionality.  There is no guarantee that a string will be interned
 /// as there are heuristics involved for it.  Additionally the string interning
 /// will only work during the template engine execution (eg: within filters etc.).
-pub fn intern(s: &str) -> Arc<String> {
-    if let Key::String(ref s) = Key::make_string_key(s) {
-        s.clone()
-    } else {
-        unreachable!()
+pub fn intern(s: &str) -> Arc<str> {
+    #[cfg(feature = "key_interning")]
+    {
+        crate::value::keyref::key_interning::try_intern(s)
+    }
+    #[cfg(not(feature = "key_interning"))]
+    {
+        Arc::from(s.to_string())
     }
 }
 
@@ -525,7 +581,7 @@ impl Value {
     /// let val = Value::from_safe_string("<em>note</em>".into());
     /// ```
     pub fn from_safe_string(value: String) -> Value {
-        ValueRepr::String(Arc::new(value), StringType::Safe).into()
+        ValueRepr::String(Arc::from(value), StringType::Safe).into()
     }
 
     /// Creates a value from a dynamic object.
@@ -697,7 +753,7 @@ impl Value {
     /// If the value is a string, return it.
     pub fn as_str(&self) -> Option<&str> {
         match &self.0 {
-            ValueRepr::String(ref s, _) => Some(s.as_str()),
+            ValueRepr::String(ref s, _) => Some(s as &str),
             _ => None,
         }
     }
@@ -785,7 +841,7 @@ impl Value {
     pub fn get_attr(&self, key: &str) -> Result<Value, Error> {
         Ok(match self.0 {
             ValueRepr::Undefined => return Err(Error::from(ErrorKind::UndefinedError)),
-            ValueRepr::Map(ref items, _) => items.get(&Key::Str(key)).cloned(),
+            ValueRepr::Map(ref items, _) => items.get(&KeyRef::Str(key)).cloned(),
             ValueRepr::Dynamic(ref dy) => match dy.kind() {
                 ObjectKind::Struct(s) => s.get_field(key),
                 ObjectKind::Plain | ObjectKind::Seq(_) => None,
@@ -803,7 +859,7 @@ impl Value {
     /// also not be created.
     pub(crate) fn get_attr_fast(&self, key: &str) -> Option<Value> {
         match self.0 {
-            ValueRepr::Map(ref items, _) => items.get(&Key::Str(key)).cloned(),
+            ValueRepr::Map(ref items, _) => items.get(&KeyRef::Str(key)).cloned(),
             ValueRepr::Dynamic(ref dy) => match dy.kind() {
                 ObjectKind::Struct(s) => s.get_field(key),
                 ObjectKind::Plain | ObjectKind::Seq(_) => None,
@@ -914,7 +970,7 @@ impl Value {
     }
 
     pub(crate) fn get_item_opt(&self, key: &Value) -> Option<Value> {
-        let key = some!(Key::from_borrowed_value(key).ok());
+        let key = KeyRef::Value(key.clone());
 
         let seq = match self.0 {
             ValueRepr::Map(ref items, _) => return items.get(&key).cloned(),
@@ -922,14 +978,16 @@ impl Value {
             ValueRepr::Dynamic(ref dy) => match dy.kind() {
                 ObjectKind::Plain => return None,
                 ObjectKind::Seq(s) => s,
-                ObjectKind::Struct(s) => match key {
-                    Key::String(ref key) => return s.get_field(key),
-                    Key::Str(key) => return s.get_field(key),
-                    _ => return None,
-                },
+                ObjectKind::Struct(s) => {
+                    return if let Some(key) = key.as_str() {
+                        s.get_field(key)
+                    } else {
+                        None
+                    };
+                }
             },
             ValueRepr::String(ref s, _) => {
-                if let Key::I64(idx) = key {
+                if let Some(idx) = key.as_i64() {
                     let idx = some!(isize::try_from(idx).ok());
                     let idx = if idx < 0 {
                         some!(s.chars().count().checked_sub(-idx as usize))
@@ -944,7 +1002,7 @@ impl Value {
             _ => return None,
         };
 
-        if let Key::I64(idx) = key {
+        if let Some(idx) = key.as_i64() {
             let idx = some!(isize::try_from(idx).ok());
             let idx = if idx < 0 {
                 some!(seq.item_count().checked_sub(-idx as usize))
@@ -1008,7 +1066,7 @@ impl Value {
         match self.0 {
             ValueRepr::Dynamic(ref dy) => return dy.call_method(state, name, args),
             ValueRepr::Map(ref map, _) => {
-                if let Some(value) = map.get(&Key::Str(name)) {
+                if let Some(value) = map.get(&KeyRef::Str(name)) {
                     return value.call(state, args);
                 }
             }
@@ -1020,24 +1078,6 @@ impl Value {
         ))
     }
 
-    pub(crate) fn try_into_key(self) -> Result<StaticKey, Error> {
-        match self.0 {
-            ValueRepr::Bool(val) => Ok(Key::Bool(val)),
-            ValueRepr::U64(v) => TryFrom::try_from(v)
-                .map(Key::I64)
-                .map_err(|_| ErrorKind::NonKey.into()),
-            ValueRepr::U128(v) => TryFrom::try_from(v.0)
-                .map(Key::I64)
-                .map_err(|_| ErrorKind::NonKey.into()),
-            ValueRepr::I64(v) => Ok(Key::I64(v)),
-            ValueRepr::I128(v) => TryFrom::try_from(v.0)
-                .map(Key::I64)
-                .map_err(|_| ErrorKind::NonKey.into()),
-            ValueRepr::String(ref s, _) => Ok(Key::String(s.clone())),
-            _ => Err(ErrorKind::NonKey.into()),
-        }
-    }
-
     /// Iterates over the value without holding a reference.
     pub(crate) fn try_iter_owned(&self) -> Result<OwnedValueIterator, Error> {
         let (iter_state, len) = match self.0 {
@@ -1170,7 +1210,7 @@ impl Serialize for Value {
                     } else {
                         for k in s.fields() {
                             let v = s.get_field(&k).unwrap_or(Value::UNDEFINED);
-                            ok!(map.serialize_entry(k.as_str(), &v));
+                            ok!(map.serialize_entry(&*k as &str, &v));
                         }
                     }
                     map.end()
@@ -1225,13 +1265,13 @@ impl fmt::Debug for OwnedValueIterator {
 
 enum ValueIteratorState {
     Empty,
-    Chars(usize, Arc<String>),
+    Chars(usize, Arc<str>),
     Seq(usize, Arc<Vec<Value>>),
     StaticStr(usize, &'static [&'static str]),
-    ArcStr(usize, Vec<Arc<String>>),
+    ArcStr(usize, Vec<Arc<str>>),
     DynSeq(usize, Arc<dyn Object>),
     #[cfg(not(feature = "preserve_order"))]
-    Map(Option<StaticKey>, Arc<ValueMap>),
+    Map(Option<KeyRef<'static>>, Arc<ValueMap>),
     #[cfg(feature = "preserve_order")]
     Map(usize, Arc<ValueMap>),
 }
@@ -1241,7 +1281,7 @@ impl ValueIteratorState {
         match self {
             ValueIteratorState::Empty => None,
             ValueIteratorState::Chars(offset, ref s) => {
-                s.as_str()[*offset..].chars().next().map(|c| {
+                (s as &str)[*offset..].chars().next().map(|c| {
                     *offset += c.len_utf8();
                     Value::from(c)
                 })
@@ -1274,7 +1314,7 @@ impl ValueIteratorState {
             #[cfg(feature = "preserve_order")]
             ValueIteratorState::Map(idx, map) => map.get_index(*idx).map(|x| {
                 *idx += 1;
-                Value::from(x.0.clone())
+                Value::from(x.0)
             }),
             #[cfg(not(feature = "preserve_order"))]
             ValueIteratorState::Map(ptr, map) => {
diff --git a/minijinja/src/value/object.rs b/minijinja/src/value/object.rs
index 7bfc6bdf..512401ea 100644
--- a/minijinja/src/value/object.rs
+++ b/minijinja/src/value/object.rs
@@ -460,7 +460,7 @@ pub trait StructObject: Send + Sync {
     /// be implemented due to lifetime restrictions.  The default implementation
     /// converts the return value of [`static_fields`](Self::static_fields) into
     /// a compatible format automatically.
-    fn fields(&self) -> Vec<Arc<String>> {
+    fn fields(&self) -> Vec<Arc<str>> {
         self.static_fields()
             .into_iter()
             .flat_map(|fields| fields.iter().copied().map(intern))
@@ -492,7 +492,7 @@ impl<T: StructObject> StructObject for Arc<T> {
     }
 
     #[inline]
-    fn fields(&self) -> Vec<Arc<String>> {
+    fn fields(&self) -> Vec<Arc<str>> {
         T::fields(self)
     }
 
@@ -514,7 +514,7 @@ impl<'a, T: StructObject + ?Sized> StructObject for &'a T {
     }
 
     #[inline]
-    fn fields(&self) -> Vec<Arc<String>> {
+    fn fields(&self) -> Vec<Arc<str>> {
         T::fields(self)
     }
 
diff --git a/minijinja/src/value/ops.rs b/minijinja/src/value/ops.rs
index ec8bb99b..fc1280dd 100644
--- a/minijinja/src/value/ops.rs
+++ b/minijinja/src/value/ops.rs
@@ -1,8 +1,7 @@
 use std::convert::{TryFrom, TryInto};
-use std::fmt::Write;
 
 use crate::error::{Error, ErrorKind};
-use crate::value::{Arc, ObjectKind, SeqObject, Value, ValueKind, ValueRepr};
+use crate::value::{KeyRef, ObjectKind, SeqObject, Value, ValueKind, ValueRepr};
 
 pub enum CoerceResult<'a> {
     I128(i128, i128),
@@ -10,7 +9,7 @@ pub enum CoerceResult<'a> {
     Str(&'a str, &'a str),
 }
 
-fn as_f64(value: &Value) -> Option<f64> {
+pub(crate) fn as_f64(value: &Value) -> Option<f64> {
     Some(match value.0 {
         ValueRepr::Bool(x) => x as i64 as f64,
         ValueRepr::U64(x) => x as f64,
@@ -248,17 +247,8 @@ pub fn neg(val: &Value) -> Result<Value, Error> {
 }
 
 /// Attempts a string concatenation.
-pub fn string_concat(mut left: Value, right: &Value) -> Value {
-    match left.0 {
-        // if we're a string and we have a single reference to it, we can
-        // directly append into ourselves and reconstruct the value
-        ValueRepr::String(ref mut s, _) => {
-            write!(Arc::make_mut(s), "{right}").ok();
-            left
-        }
-        // otherwise we use format! to concat the two values
-        _ => Value::from(format!("{left}{right}")),
-    }
+pub fn string_concat(left: Value, right: &Value) -> Value {
+    Value::from(format!("{left}{right}"))
 }
 
 /// Implements a containment operation on values.
@@ -277,11 +267,7 @@ pub fn contains(container: &Value, value: &Value) -> Result<Value, Error> {
     } else if let Some(seq) = container.as_seq() {
         seq.iter().any(|item| &item == value)
     } else if let ValueRepr::Map(ref map, _) = container.0 {
-        let key = match value.clone().try_into_key() {
-            Ok(key) => key,
-            Err(_) => return Ok(Value::from(false)),
-        };
-        map.get(&key).is_some()
+        map.get(&KeyRef::Value(value.clone())).is_some()
     } else {
         return Err(Error::new(
             ErrorKind::InvalidOperation,
diff --git a/minijinja/src/value/serialize.rs b/minijinja/src/value/serialize.rs
index e6d56dcb..ca7ea4b7 100644
--- a/minijinja/src/value/serialize.rs
+++ b/minijinja/src/value/serialize.rs
@@ -3,14 +3,14 @@ use std::fmt;
 
 use serde::{ser, Serialize, Serializer};
 
-use crate::key::{Key, KeySerializer, StaticKey};
+use crate::utils::untrusted_size_hint;
 use crate::value::{
-    value_map_with_capacity, Arc, MapType, Packed, StringType, Value, ValueMap, ValueRepr,
+    value_map_with_capacity, Arc, KeyRef, MapType, Packed, StringType, Value, ValueMap, ValueRepr,
     VALUE_HANDLES, VALUE_HANDLE_MARKER,
 };
 
 #[derive(Debug)]
-pub struct InvalidValue(Arc<String>);
+pub struct InvalidValue(Arc<str>);
 
 impl std::error::Error for InvalidValue {}
 
@@ -25,7 +25,7 @@ impl serde::ser::Error for InvalidValue {
     where
         T: fmt::Display,
     {
-        InvalidValue(Arc::new(msg.to_string()))
+        InvalidValue(Arc::from(msg.to_string()))
     }
 }
 
@@ -107,11 +107,11 @@ impl Serializer for ValueSerializer {
     }
 
     fn serialize_char(self, v: char) -> Result<Value, InvalidValue> {
-        Ok(ValueRepr::String(Arc::new(v.to_string()), StringType::Normal).into())
+        Ok(ValueRepr::String(Arc::from(v.to_string()), StringType::Normal).into())
     }
 
     fn serialize_str(self, value: &str) -> Result<Value, InvalidValue> {
-        Ok(ValueRepr::String(Arc::new(value.to_owned()), StringType::Normal).into())
+        Ok(ValueRepr::String(Arc::from(value.to_owned()), StringType::Normal).into())
     }
 
     fn serialize_bytes(self, value: &[u8]) -> Result<Value, InvalidValue> {
@@ -177,19 +177,19 @@ impl Serializer for ValueSerializer {
         T: Serialize,
     {
         let mut map = value_map_with_capacity(1);
-        map.insert(Key::Str(variant), transform(value));
+        map.insert(KeyRef::Str(variant), transform(value));
         Ok(ValueRepr::Map(Arc::new(map), MapType::Normal).into())
     }
 
     fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, InvalidValue> {
         Ok(SerializeSeq {
-            elements: Vec::with_capacity(len.unwrap_or(0)),
+            elements: Vec::with_capacity(untrusted_size_hint(len.unwrap_or(0))),
         })
     }
 
     fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, InvalidValue> {
         Ok(SerializeTuple {
-            elements: Vec::with_capacity(len),
+            elements: Vec::with_capacity(untrusted_size_hint(len)),
         })
     }
 
@@ -199,7 +199,7 @@ impl Serializer for ValueSerializer {
         len: usize,
     ) -> Result<Self::SerializeTupleStruct, InvalidValue> {
         Ok(SerializeTupleStruct {
-            fields: Vec::with_capacity(len),
+            fields: Vec::with_capacity(untrusted_size_hint(len)),
         })
     }
 
@@ -212,7 +212,7 @@ impl Serializer for ValueSerializer {
     ) -> Result<Self::SerializeTupleVariant, InvalidValue> {
         Ok(SerializeTupleVariant {
             name: variant,
-            fields: Vec::with_capacity(len),
+            fields: Vec::with_capacity(untrusted_size_hint(len)),
         })
     }
 
@@ -330,7 +330,7 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant {
     fn end(self) -> Result<Value, InvalidValue> {
         let mut map = value_map_with_capacity(1);
         map.insert(
-            Key::Str(self.name),
+            KeyRef::Str(self.name),
             Value(ValueRepr::Seq(self.fields.into())),
         );
         Ok(Value(ValueRepr::Map(map.into(), MapType::Normal)))
@@ -339,7 +339,7 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant {
 
 pub struct SerializeMap {
     entries: ValueMap,
-    key: Option<StaticKey>,
+    key: Option<Value>,
 }
 
 impl ser::SerializeMap for SerializeMap {
@@ -350,7 +350,7 @@ impl ser::SerializeMap for SerializeMap {
     where
         T: Serialize,
     {
-        match key.serialize(KeySerializer) {
+        match key.serialize(ValueSerializer) {
             Ok(key) => self.key = Some(key),
             Err(_) => self.key = None,
         }
@@ -362,7 +362,7 @@ impl ser::SerializeMap for SerializeMap {
         T: Serialize,
     {
         if let Some(key) = self.key.take() {
-            self.entries.insert(key, transform(value));
+            self.entries.insert(KeyRef::Value(key), transform(value));
         }
         Ok(())
     }
@@ -383,8 +383,8 @@ impl ser::SerializeMap for SerializeMap {
         K: Serialize,
         V: Serialize,
     {
-        if let Ok(key) = key.serialize(KeySerializer) {
-            self.entries.insert(key, transform(value));
+        if let Ok(key) = key.serialize(ValueSerializer) {
+            self.entries.insert(KeyRef::Value(key), transform(value));
         }
         Ok(())
     }
@@ -406,7 +406,7 @@ impl ser::SerializeStruct for SerializeStruct {
     where
         T: Serialize,
     {
-        self.fields.insert(Key::Str(key), transform(value));
+        self.fields.insert(KeyRef::Str(key), transform(value));
         Ok(())
     }
 
@@ -432,7 +432,7 @@ impl ser::SerializeStructVariant for SerializeStructVariant {
     where
         T: Serialize,
     {
-        self.map.insert(Key::Str(key), transform(value));
+        self.map.insert(KeyRef::Str(key), transform(value));
         Ok(())
     }
 
diff --git a/minijinja/src/vm/closure_object.rs b/minijinja/src/vm/closure_object.rs
index e156f413..d980d5e5 100644
--- a/minijinja/src/vm/closure_object.rs
+++ b/minijinja/src/vm/closure_object.rs
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
 use std::fmt;
 use std::sync::{Arc, Mutex};
 
-use crate::key::{Key, StaticKey};
 use crate::value::{Object, ObjectKind, StructObject, Value};
 
 /// Utility to enclose values for macros.
@@ -10,24 +9,21 @@ use crate::value::{Object, ObjectKind, StructObject, Value};
 /// See `closure` on the [`Frame`] for how it's used.
 #[derive(Debug, Default)]
 pub(crate) struct Closure {
-    values: Mutex<BTreeMap<StaticKey, Value>>,
+    values: Mutex<BTreeMap<Arc<str>, Value>>,
 }
 
 impl Closure {
     /// Stores a value by key in the closure.
     pub fn store(&self, key: &str, value: Value) {
-        self.values
-            .lock()
-            .unwrap()
-            .insert(StaticKey::from(key), value);
+        self.values.lock().unwrap().insert(Arc::from(key), value);
     }
 
     /// Upset a value into the closure.
     #[cfg(feature = "macros")]
     pub fn store_if_missing<F: FnOnce() -> Value>(&self, key: &str, f: F) {
         let mut values = self.values.lock().unwrap();
-        if !values.contains_key(&Key::Str(key)) {
-            values.insert(key.into(), f());
+        if !values.contains_key(key) {
+            values.insert(Arc::from(key), f());
         }
     }
 }
@@ -49,20 +45,11 @@ impl Object for Closure {
 }
 
 impl StructObject for Closure {
-    fn fields(&self) -> Vec<Arc<String>> {
-        self.values
-            .lock()
-            .unwrap()
-            .keys()
-            .filter_map(|x| match x {
-                Key::String(s) => Some(s.clone()),
-                Key::Str(s) => Some(Arc::new(s.to_string())),
-                _ => None,
-            })
-            .collect()
+    fn fields(&self) -> Vec<Arc<str>> {
+        self.values.lock().unwrap().keys().cloned().collect()
     }
 
     fn get_field(&self, name: &str) -> Option<Value> {
-        self.values.lock().unwrap().get(&Key::Str(name)).cloned()
+        self.values.lock().unwrap().get(name).cloned()
     }
 }
diff --git a/minijinja/src/vm/macro_object.rs b/minijinja/src/vm/macro_object.rs
index 974a1213..d843f780 100644
--- a/minijinja/src/vm/macro_object.rs
+++ b/minijinja/src/vm/macro_object.rs
@@ -3,16 +3,17 @@ use std::fmt;
 use std::sync::Arc;
 
 use crate::error::{Error, ErrorKind};
-use crate::key::Key;
 use crate::output::Output;
 use crate::utils::AutoEscape;
-use crate::value::{MapType, Object, ObjectKind, StringType, StructObject, Value, ValueRepr};
+use crate::value::{
+    KeyRef, MapType, Object, ObjectKind, StringType, StructObject, Value, ValueRepr,
+};
 use crate::vm::state::State;
 use crate::vm::Vm;
 
 pub(crate) struct MacroData {
-    pub name: Arc<String>,
-    pub arg_spec: Vec<Arc<String>>,
+    pub name: Arc<str>,
+    pub arg_spec: Vec<Arc<str>>,
     // because values need to be 'static, we can't hold a reference to the
     // instructions that declared the macro.  Instead of that we place the
     // reference to the macro instruction (and the jump offset) in the
@@ -61,7 +62,7 @@ impl Object for Macro {
         let mut arg_values = Vec::with_capacity(self.data.arg_spec.len());
         for (idx, name) in self.data.arg_spec.iter().enumerate() {
             let kwarg = match kwargs {
-                Some(kwargs) => kwargs.get(&Key::Str(name)),
+                Some(kwargs) => kwargs.get(&KeyRef::Str(name)),
                 _ => None,
             };
             arg_values.push(match (args.get(idx), kwarg) {
@@ -84,7 +85,7 @@ impl Object for Macro {
             kwargs_used.insert("caller");
             Some(
                 kwargs
-                    .and_then(|x| x.get(&Key::Str("caller")).cloned())
+                    .and_then(|x| x.get(&KeyRef::Str("caller")).cloned())
                     .unwrap_or(Value::UNDEFINED),
             )
         } else {
diff --git a/minijinja/src/vm/mod.rs b/minijinja/src/vm/mod.rs
index ed07e7b1..bacfa937 100644
--- a/minijinja/src/vm/mod.rs
+++ b/minijinja/src/vm/mod.rs
@@ -9,8 +9,10 @@ use crate::compiler::instructions::{
 use crate::environment::Environment;
 use crate::error::{Error, ErrorKind};
 use crate::output::{CaptureMode, Output};
-use crate::utils::{AutoEscape, UndefinedBehavior};
-use crate::value::{ops, value_map_with_capacity, value_optimization, MapType, Value, ValueRepr};
+use crate::utils::{untrusted_size_hint, AutoEscape, UndefinedBehavior};
+use crate::value::{
+    ops, value_map_with_capacity, value_optimization, KeyRef, MapType, Value, ValueRepr,
+};
 use crate::vm::context::{Context, Frame, LoopState, Stack};
 use crate::vm::loop_object::Loop;
 use crate::vm::state::BlockStack;
@@ -315,8 +317,8 @@ impl<'env> Vm<'env> {
                     let mut map = value_map_with_capacity(*pair_count);
                     for _ in 0..*pair_count {
                         let value = stack.pop();
-                        let key = ctx_ok!(stack.pop().try_into_key());
-                        map.insert(key, value);
+                        let key = stack.pop();
+                        map.insert(KeyRef::Value(key), value);
                     }
                     stack.push(Value(ValueRepr::Map(map.into(), MapType::Normal)))
                 }
@@ -324,13 +326,13 @@ impl<'env> Vm<'env> {
                     let mut map = value_map_with_capacity(*pair_count);
                     for _ in 0..*pair_count {
                         let value = stack.pop();
-                        let key = stack.pop().try_into_key().unwrap();
-                        map.insert(key, value);
+                        let key = stack.pop();
+                        map.insert(KeyRef::Value(key), value);
                     }
                     stack.push(Value(ValueRepr::Map(map.into(), MapType::Kwargs)))
                 }
                 Instruction::BuildList(count) => {
-                    let mut v = Vec::with_capacity(*count);
+                    let mut v = Vec::with_capacity(untrusted_size_hint(*count));
                     for _ in 0..*count {
                         v.push(stack.pop());
                     }
@@ -617,7 +619,7 @@ impl<'env> Vm<'env> {
                     let locals = state.ctx.current_locals_mut();
                     let mut module = value_map_with_capacity(locals.len());
                     for (key, value) in locals.iter() {
-                        module.insert((*key).into(), value.clone());
+                        module.insert(KeyRef::Value(Value::from(*key)), value.clone());
                     }
                     stack.push(Value(ValueRepr::Map(module.into(), MapType::Normal)));
                 }
@@ -941,7 +943,7 @@ impl<'env> Vm<'env> {
         Arc::make_mut(&mut state.macros).push((state.instructions, offset));
         stack.push(Value::from_object(Macro {
             data: Arc::new(MacroData {
-                name: Arc::new(name.to_string()),
+                name: Arc::from(name.to_string()),
                 arg_spec,
                 macro_ref_id,
                 closure,


Test patch:
diff --git a/minijinja/tests/test_value.rs b/minijinja/tests/test_value.rs
index 622758eb..9448698a 100644
--- a/minijinja/tests/test_value.rs
+++ b/minijinja/tests/test_value.rs
@@ -1,5 +1,4 @@
 use std::fmt;
-use std::sync::Arc;
 
 use insta::assert_snapshot;
 use similar_asserts::assert_eq;
@@ -191,13 +190,6 @@ fn test_builtin_seq_objects() {
     assert_snapshot!(rv, @r###"["foo", "bar"]"###);
 }
 
-#[test]
-fn test_value_string_interop() {
-    let s = Arc::new(String::from("Hello"));
-    let v = Value::from(s);
-    assert_eq!(v.as_str(), Some("Hello"));
-}
-
 #[test]
 fn test_value_object_interface() {
     let val = Value::from_seq_object(vec![1u32, 2, 3, 4]);
@@ -392,3 +384,20 @@ fn test_seq_object_borrow() {
         Value::from("HELLO42")
     );
 }
+
+#[test]
+fn test_complex_key() {
+    let value = Value::from_iter([
+        (Value::from_iter([0u32, 0u32]), "origin"),
+        (Value::from_iter([0u32, 1u32]), "right"),
+    ]);
+
+    assert_eq!(
+        value.get_item(&Value::from_iter([0, 0])).ok(),
+        Some(Value::from("origin"))
+    );
+    assert_eq!(
+        value.get_item(&Value::from_iter([0, 42])).ok(),
+        Some(Value::UNDEFINED)
+    );
+}