Repository: tweag/nickel
Pull request #426 (2021-10-19 18:55:08)

Rc richterm
This branch adds the sharing of `Term`s between `RichTerm`s.

Criterion shows a significant improvement in performance (you can find the results in the attached file: decompress and open `report/index.html`)
[criterion.tar.gz](https://github.com/tweag/nickel/files/7375946/criterion.tar.gz)

I consider this branch not ready to merge for now because there is some "ugliness" in the code, that could be easily fixed by adding nice macros.


Patch:
diff --git a/src/cache.rs b/src/cache.rs
index 64f12e25..1ce800a2 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -4,7 +4,7 @@ use crate::error::{Error, ImportError, ParseError, ParseErrors, TypecheckError};
 use crate::parser::lexer::Lexer;
 use crate::position::TermPos;
 use crate::stdlib as nickel_stdlib;
-use crate::term::{RichTerm, Term};
+use crate::term::{RichTerm, SharedTerm, Term};
 use crate::transform::import_resolution;
 use crate::typecheck;
 use crate::typecheck::{linearization::StubHost, type_check};
@@ -497,7 +497,7 @@ impl Cache {
                 } = self.terms.remove(&file_id).unwrap();
 
                 if state < EntryState::Transforming {
-                    match term.term.as_mut() {
+                    match SharedTerm::make_mut(&mut term.term) {
                         Term::Record(ref mut map, _) => {
                             let map_res = std::mem::take(map)
                                 .into_iter()
diff --git a/src/eval/merge.rs b/src/eval/merge.rs
index 4513d343..231265e9 100644
--- a/src/eval/merge.rs
+++ b/src/eval/merge.rs
@@ -55,7 +55,9 @@ use crate::error::EvalError;
 use crate::eval::{CallStack, Closure, Environment};
 use crate::label::Label;
 use crate::position::TermPos;
-use crate::term::{make as mk_term, BinaryOp, Contract, MetaValue, RecordAttrs, RichTerm, Term};
+use crate::term::{
+    make as mk_term, BinaryOp, Contract, MetaValue, RecordAttrs, RichTerm, SharedTerm, Term,
+};
 use crate::transform::Closurizable;
 use std::collections::HashMap;
 
@@ -115,7 +117,7 @@ pub fn merge(
         pos: pos2,
     } = t2;
 
-    match (*t1, *t2) {
+    match (t1.into_owned(), t2.into_owned()) {
         // Merge is idempotent on basic terms
         (Term::Bool(b1), Term::Bool(b2)) => {
             if b1 == b2 {
@@ -126,11 +128,11 @@ pub fn merge(
             } else {
                 Err(EvalError::MergeIncompatibleArgs(
                     RichTerm {
-                        term: Box::new(Term::Bool(b1)),
+                        term: SharedTerm::new(Term::Bool(b1)),
                         pos: pos1,
                     },
                     RichTerm {
-                        term: Box::new(Term::Bool(b2)),
+                        term: SharedTerm::new(Term::Bool(b2)),
                         pos: pos2,
                     },
                     pos_op,
@@ -146,11 +148,11 @@ pub fn merge(
             } else {
                 Err(EvalError::MergeIncompatibleArgs(
                     RichTerm {
-                        term: Box::new(Term::Num(n1)),
+                        term: SharedTerm::new(Term::Num(n1)),
                         pos: pos1,
                     },
                     RichTerm {
-                        term: Box::new(Term::Num(n2)),
+                        term: SharedTerm::new(Term::Num(n2)),
                         pos: pos2,
                     },
                     pos_op,
@@ -166,11 +168,11 @@ pub fn merge(
             } else {
                 Err(EvalError::MergeIncompatibleArgs(
                     RichTerm {
-                        term: Box::new(Term::Str(s1)),
+                        term: SharedTerm::new(Term::Str(s1)),
                         pos: pos1,
                     },
                     RichTerm {
-                        term: Box::new(Term::Str(s2)),
+                        term: SharedTerm::new(Term::Str(s2)),
                         pos: pos2,
                     },
                     pos_op,
@@ -186,11 +188,11 @@ pub fn merge(
             } else {
                 Err(EvalError::MergeIncompatibleArgs(
                     RichTerm {
-                        term: Box::new(Term::Lbl(l1)),
+                        term: SharedTerm::new(Term::Lbl(l1)),
                         pos: pos1,
                     },
                     RichTerm {
-                        term: Box::new(Term::Lbl(l2)),
+                        term: SharedTerm::new(Term::Lbl(l2)),
                         pos: pos2,
                     },
                     pos_op,
@@ -327,8 +329,6 @@ pub fn merge(
              * the same trick as in the evaluation of the operator DynExtend, and replace each such
              * term by a variable bound to an appropriate closure in the environment
              */
-            let mut m = HashMap::new();
-            let mut env = Environment::new();
             rev_thunks(m1.values_mut(), &mut env1);
             rev_thunks(m2.values_mut(), &mut env2);
             let (left, center, right) = hashmap::split(m1, m2);
@@ -344,6 +344,9 @@ pub fn merge(
                 _ => (),
             };
 
+            let mut m = HashMap::with_capacity(left.len() + center.len() + right.len());
+            let mut env = Environment::new();
+
             for (field, t) in left.into_iter() {
                 m.insert(field, t.closurize(&mut env, env1.clone()));
             }
@@ -370,11 +373,11 @@ pub fn merge(
         //The following cases are either errors or not yet implemented
         (t1_, t2_) => Err(EvalError::MergeIncompatibleArgs(
             RichTerm {
-                term: Box::new(t1_),
+                term: SharedTerm::new(t1_),
                 pos: pos1,
             },
             RichTerm {
-                term: Box::new(t2_),
+                term: SharedTerm::new(t2_),
                 pos: pos2,
             },
             pos_op,
@@ -442,7 +445,7 @@ fn rev_thunks<'a, I: Iterator<Item = &'a mut RichTerm>>(map: I, env: &mut Enviro
             let reverted = env.get(&id).unwrap().revert();
             let fresh_id = fresh_var();
             env.insert(fresh_id.clone(), reverted);
-            *rt.term = Term::Var(fresh_id);
+            *(SharedTerm::make_mut(&mut rt.term)) = Term::Var(fresh_id);
         }
         // Otherwise, if it is not a variable after the share normal form transformations, it
         // should be a constant and we don't need to revert anything
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index da330724..f71a9919 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -87,13 +87,17 @@
 //! probably suboptimal for a functional language and is unable to collect cyclic data, which may
 //! appear inside recursive records in the future. An adapted garbage collector is probably
 //! something to consider at some point.
+
 use crate::{
     cache::ImportResolver,
     environment::Environment as GenericEnvironment,
     error::EvalError,
     identifier::Ident,
-    mk_app,
-    term::{make as mk_term, BinaryOp, BindingType, MetaValue, RichTerm, StrChunk, Term, UnaryOp},
+    match_sharedterm, mk_app,
+    term::{
+        make as mk_term, BinaryOp, BindingType, MetaValue, RichTerm, SharedTerm, StrChunk, Term,
+        UnaryOp,
+    },
 };
 
 pub mod callstack;
@@ -147,21 +151,19 @@ pub enum EnvBuildError {
 
 /// Add the bindings of a record to an environment. Ignore the fields defined by interpolation.
 pub fn env_add_term(env: &mut Environment, rt: RichTerm) -> Result<(), EnvBuildError> {
-    let RichTerm { term, pos } = rt;
-
-    match *term {
-        Term::Record(bindings, _) | Term::RecRecord(bindings, ..) => {
-            let ext = bindings.into_iter().map(|(id, t)| {
-                (
-                    id,
-                    Thunk::new(Closure::atomic_closure(t), IdentKind::Record),
-                )
-            });
-
-            env.extend(ext);
-            Ok(())
-        }
-        t => Err(EnvBuildError::NotARecord(RichTerm::new(t, pos))),
+    match_sharedterm! {rt.term, with {
+            Term::Record(bindings, _) | Term::RecRecord(bindings, ..) => {
+                let ext = bindings.into_iter().map(|(id, t)| {
+                    (
+                        id,
+                        Thunk::new(Closure::atomic_closure(t), IdentKind::Record),
+                    )
+                });
+
+                env.extend(ext);
+                Ok(())
+            },
+        } else Err(EnvBuildError::NotARecord(rt))
     }
 }
 
@@ -249,7 +251,7 @@ where
 {
     let (mut rt, env) = eval_closure(Closure::atomic_closure(t), global_env, resolver, false)?;
 
-    match *rt.term {
+    match *SharedTerm::make_mut(&mut rt.term) {
         Term::MetaValue(ref mut meta) => {
             if let Some(t) = meta.value.take() {
                 let (evaluated, env) =
@@ -301,22 +303,21 @@ where
     loop {
         let Closure {
             body: RichTerm {
-                term: boxed_term,
+                term: shared_term,
                 pos,
             },
             mut env,
         } = clos;
-        let term = *boxed_term;
 
         if let Some(strict) = stack.pop_strictness_marker() {
             enriched_strict = strict;
         }
 
-        clos = match term {
+        clos = match &*shared_term {
             Term::Var(x) => {
                 let mut thunk = env
-                    .get(&x)
-                    .or_else(|| global_env.get(&x))
+                    .get(x)
+                    .or_else(|| global_env.get(x))
                     .ok_or_else(|| EvalError::UnboundIdentifier(x.clone(), pos))?;
                 std::mem::drop(env); // thunk may be a 1RC pointer
 
@@ -334,8 +335,7 @@ where
                         thunk.set_evaluated();
                     }
                 }
-
-                call_stack.enter_var(thunk.ident_kind(), x, pos);
+                call_stack.enter_var(thunk.ident_kind(), x.clone(), pos);
                 thunk.into_closure()
             }
             Term::App(t1, t2) => {
@@ -347,16 +347,19 @@ where
                 enriched_strict = true;
                 stack.push_arg(
                     Closure {
-                        body: t2,
+                        body: t2.clone(),
                         env: env.clone(),
                     },
                     pos,
                 );
-                Closure { body: t1, env }
+                Closure {
+                    body: t1.clone(),
+                    env,
+                }
             }
             Term::Let(x, s, t, btype) => {
                 let closure = Closure {
-                    body: s,
+                    body: s.clone(),
                     env: env.clone(),
                 };
 
@@ -365,8 +368,11 @@ where
                     BindingType::Revertible => Thunk::new_rev(closure, IdentKind::Let),
                 };
 
-                env.insert(x, thunk);
-                Closure { body: t, env }
+                env.insert(x.clone(), thunk);
+                Closure {
+                    body: t.clone(),
+                    env,
+                }
             }
             Term::Switch(exp, cases, default) => {
                 let has_default = default.is_some();
@@ -374,7 +380,7 @@ where
                 if let Some(t) = default {
                     stack.push_arg(
                         Closure {
-                            body: t,
+                            body: t.clone(),
                             env: env.clone(),
                         },
                         pos,
@@ -383,14 +389,14 @@ where
 
                 stack.push_arg(
                     Closure {
-                        body: RichTerm::new(Term::Record(cases, Default::default()), pos),
+                        body: RichTerm::new(Term::Record(cases.clone(), Default::default()), pos),
                         env: env.clone(),
                     },
                     pos,
                 );
 
                 Closure {
-                    body: RichTerm::new(Term::Op1(UnaryOp::Switch(has_default), exp), pos),
+                    body: RichTerm::new(Term::Op1(UnaryOp::Switch(has_default), exp.clone()), pos),
                     env,
                 }
             }
@@ -399,8 +405,11 @@ where
                     stack.push_strictness(enriched_strict);
                 }
                 enriched_strict = true;
-                stack.push_op_cont(OperationCont::Op1(op, t.pos), call_stack.len(), pos);
-                Closure { body: t, env }
+                stack.push_op_cont(OperationCont::Op1(op.clone(), t.pos), call_stack.len(), pos);
+                Closure {
+                    body: t.clone(),
+                    env,
+                }
             }
             Term::Op2(op, fst, snd) => {
                 let strict_op = op.is_strict();
@@ -410,9 +419,9 @@ where
                 enriched_strict = strict_op;
                 stack.push_op_cont(
                     OperationCont::Op2First(
-                        op,
+                        op.clone(),
                         Closure {
-                            body: snd,
+                            body: snd.clone(),
                             env: env.clone(),
                         },
                         fst.pos,
@@ -420,9 +429,12 @@ where
                     call_stack.len(),
                     pos,
                 );
-                Closure { body: fst, env }
+                Closure {
+                    body: fst.clone(),
+                    env,
+                }
             }
-            Term::OpN(op, mut args) => {
+            Term::OpN(op, args) => {
                 let strict_op = op.is_strict();
                 if enriched_strict != strict_op {
                     stack.push_strictness(enriched_strict);
@@ -431,22 +443,23 @@ where
 
                 // Arguments are passed as a stack to the operation continuation, so we reverse the
                 // original list.
-                args.reverse();
-                let fst = args
-                    .pop()
+                let mut args_iter = args.iter();
+                let fst = args_iter
+                    .next()
+                    .cloned()
                     .ok_or_else(|| EvalError::NotEnoughArgs(op.arity(), op.to_string(), pos))?;
 
-                let pending: Vec<Closure> = args
-                    .into_iter()
+                let pending: Vec<Closure> = args_iter
+                    .rev()
                     .map(|t| Closure {
-                        body: t,
+                        body: t.clone(),
                         env: env.clone(),
                     })
                     .collect();
 
                 stack.push_op_cont(
                     OperationCont::OpN {
-                        op,
+                        op: op.clone(),
                         evaluated: Vec::with_capacity(pending.len() + 1),
                         pending,
                         current_pos: fst.pos,
@@ -457,30 +470,33 @@ where
 
                 Closure { body: fst, env }
             }
-            Term::StrChunks(mut chunks) => match chunks.pop() {
-                None => Closure {
-                    body: Term::Str(String::new()).into(),
-                    env: Environment::new(),
-                },
-                Some(chunk) => {
-                    let (arg, indent) = match chunk {
-                        StrChunk::Literal(s) => (Term::Str(s).into(), 0),
-                        StrChunk::Expr(e, indent) => (e, indent),
-                    };
-
-                    if !enriched_strict {
-                        stack.push_strictness(enriched_strict);
-                    }
-                    enriched_strict = true;
-                    stack.push_str_chunks(chunks.into_iter());
-                    stack.push_str_acc(String::new(), indent, env.clone());
+            Term::StrChunks(chunks) => {
+                let mut chunks_iter = chunks.iter();
+                match chunks_iter.next_back() {
+                    None => Closure {
+                        body: Term::Str(String::new()).into(),
+                        env: Environment::new(),
+                    },
+                    Some(chunk) => {
+                        let (arg, indent) = match chunk {
+                            StrChunk::Literal(s) => (Term::Str(s.clone()).into(), 0),
+                            StrChunk::Expr(e, indent) => (e.clone(), *indent),
+                        };
+
+                        if !enriched_strict {
+                            stack.push_strictness(enriched_strict);
+                        }
+                        enriched_strict = true;
+                        stack.push_str_chunks(chunks_iter.cloned());
+                        stack.push_str_acc(String::new(), indent, env.clone());
 
-                    Closure {
-                        body: RichTerm::new(Term::Op1(UnaryOp::ChunksConcat(), arg), pos),
-                        env,
+                        Closure {
+                            body: RichTerm::new(Term::Op1(UnaryOp::ChunksConcat(), arg), pos),
+                            env,
+                        }
                     }
                 }
-            },
+            }
             Term::RecRecord(ts, dyn_fields, attrs) => {
                 // Thanks to the share normal form transformation, the content is either a constant or a
                 // variable.
@@ -509,30 +525,30 @@ where
                 )?;
 
                 let new_ts = ts.into_iter().map(|(id, rt)| {
-                    let RichTerm { term, pos } = rt;
-                    match *term {
+                    let pos = rt.pos;
+                    match &*rt.term {
                         Term::Var(var_id) => {
                             // We already checked for unbound identifier in the previous fold,
                             // so function should always succeed
-                            let mut thunk = env.get(&var_id).unwrap();
+                            let mut thunk = env.get(var_id).unwrap();
                             thunk.borrow_mut().env.extend(
                                 rec_env
                                     .iter_elems()
                                     .map(|(id, thunk)| (id.clone(), thunk.clone())),
                             );
                             (
-                                id,
+                                id.clone(),
                                 RichTerm {
-                                    term: Box::new(Term::Var(var_id)),
+                                    term: SharedTerm::new(Term::Var(var_id.clone())),
                                     pos,
                                 },
                             )
                         }
-                        _ => (id, RichTerm { term, pos }),
+                        _ => (id.clone(), rt.clone()),
                     }
                 });
 
-                let static_part = RichTerm::new(Term::Record(new_ts.collect(), attrs), pos);
+                let static_part = RichTerm::new(Term::Record(new_ts.collect(), attrs.clone()), pos);
 
                 // Transform the static part `{stat1 = val1, ..., statn = valn}` and the dynamic
                 // part `{exp1 = dyn_val1, ..., expm = dyn_valm}` to a sequence of extensions
@@ -544,10 +560,11 @@ where
                     .try_fold::<_, _, Result<RichTerm, EvalError>>(
                         static_part,
                         |acc, (id_t, t)| {
-                            let RichTerm { term, pos } = t;
-                            match *term {
+                            let id_t = id_t.clone();
+                            let pos = t.pos;
+                            match &*t.term {
                                 Term::Var(var_id) => {
-                                    let mut thunk = env.get(&var_id).ok_or_else(|| {
+                                    let mut thunk = env.get(var_id).ok_or_else(|| {
                                         EvalError::UnboundIdentifier(var_id.clone(), pos)
                                     })?;
 
@@ -558,13 +575,13 @@ where
                                     );
                                     Ok(Term::App(
                                         mk_term::op2(BinaryOp::DynExtend(), id_t, acc),
-                                        mk_term::var(var_id).with_pos(pos),
+                                        mk_term::var(var_id.clone()).with_pos(pos),
                                     )
                                     .into())
                                 }
                                 _ => Ok(Term::App(
                                     mk_term::op2(BinaryOp::DynExtend(), id_t, acc),
-                                    RichTerm { term, pos },
+                                    t.clone(),
                                 )
                                 .into()),
                             }
@@ -577,7 +594,7 @@ where
                 }
             }
             // Unwrapping of enriched terms
-            Term::MetaValue(mut meta) if enriched_strict => {
+            Term::MetaValue(meta) if enriched_strict => {
                 if meta.value.is_some() {
                     /* Since we are forcing a metavalue, we are morally evaluating `force t` rather
                      * than `t` iteself.  Updating a thunk after having performed this forcing may
@@ -587,7 +604,7 @@ where
                      */
                     let update_closure = Closure {
                         body: RichTerm {
-                            term: Box::new(Term::MetaValue(meta)),
+                            term: shared_term.clone(),
                             pos,
                         },
                         env,
@@ -599,19 +616,23 @@ where
                         env,
                     } = update_closure;
 
-                    match *term {
+                    match term.into_owned() {
                         Term::MetaValue(MetaValue {
                             value: Some(inner), ..
                         }) => Closure { body: inner, env },
                         _ => unreachable!(),
                     }
                 } else {
-                    let label = meta.contracts.pop().or(meta.types).map(|ctr| ctr.label);
+                    let label = meta
+                        .contracts
+                        .last()
+                        .or(meta.types.as_ref())
+                        .map(|ctr| ctr.label.clone());
                     return Err(EvalError::MissingFieldDef(label, call_stack));
                 }
             }
             Term::ResolvedImport(id) => {
-                if let Some(t) = resolver.get(id) {
+                if let Some(t) = resolver.get(*id) {
                     Closure::atomic_closure(t)
                 } else {
                     return Err(EvalError::InternalError(
@@ -630,7 +651,7 @@ where
             _ if stack.is_top_thunk() || stack.is_top_cont() => {
                 clos = Closure {
                     body: RichTerm {
-                        term: Box::new(term),
+                        term: shared_term,
                         pos,
                     },
                     env,
@@ -646,10 +667,13 @@ where
             Term::Fun(x, t) => {
                 if let Some((thunk, pos_app)) = stack.pop_arg_as_thunk() {
                     call_stack.enter_fun(pos_app);
-                    env.insert(x, thunk);
-                    Closure { body: t, env }
+                    env.insert(x.clone(), thunk);
+                    Closure {
+                        body: t.clone(),
+                        env,
+                    }
                 } else {
-                    return Ok((RichTerm::new(Term::Fun(x, t), pos), env));
+                    return Ok((RichTerm::new(Term::Fun(x.clone(), t.clone()), pos), env));
                 }
             }
             // Otherwise, this is either an ill-formed application, or we are done
@@ -657,14 +681,14 @@ where
                 if let Some((arg, pos_app)) = stack.pop_arg() {
                     return Err(EvalError::NotAFunc(
                         RichTerm {
-                            term: Box::new(t),
+                            term: shared_term.clone(),
                             pos,
                         },
                         arg.body,
                         pos_app,
                     ));
                 } else {
-                    return Ok((RichTerm::new(t, pos), env));
+                    return Ok((RichTerm::new(t.clone(), pos), env));
                 }
             }
         }
@@ -692,7 +716,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
         bound: Cow<HashSet<Ident>>,
     ) -> RichTerm {
         let RichTerm { term, pos } = rt;
-        match *term {
+        match term.into_owned() {
             Term::Var(id) if !bound.as_ref().contains(&id) => env
                 .get(&id)
                 .or_else(|| global_env.get(&id))
diff --git a/src/eval/operation.rs b/src/eval/operation.rs
index 811aee5c..634c721e 100644
--- a/src/eval/operation.rs
+++ b/src/eval/operation.rs
@@ -13,11 +13,11 @@ use crate::error::EvalError;
 use crate::eval::{subst, CallStack, Closure, Environment};
 use crate::identifier::Ident;
 use crate::label::ty_path;
-use crate::mk_record;
 use crate::position::TermPos;
 use crate::term::make as mk_term;
 use crate::term::{BinaryOp, NAryOp, RichTerm, StrChunk, Term, UnaryOp};
 use crate::transform::Closurizable;
+use crate::{match_sharedterm, mk_record};
 use crate::{mk_app, mk_fun, mk_opn};
 use crate::{serialize, serialize::ExportFormat};
 use md5::digest::Digest;
@@ -203,16 +203,17 @@ fn process_unary_operation(
         // The syntax should not allow partially applied boolean operators.
         {
             if let Some((next, ..)) = stack.pop_arg() {
-                match *t {
+                match &*t {
                     Term::Bool(true) => Ok(next),
                     // FIXME: this does not check that the second argument is actually a boolean.
                     // This means `true && 2` silently evaluates to `2`. This is simpler and more
                     // efficient, but can make debugging harder. In any case, it should be solved
                     // only once primary operators have better support for laziness in some
                     // arguments.
-                    b @ Term::Bool(false) => {
-                        Ok(Closure::atomic_closure(RichTerm::new(b, pos_op_inh)))
-                    }
+                    Term::Bool(false) => Ok(Closure::atomic_closure(RichTerm {
+                        term: t,
+                        pos: pos_op_inh,
+                    })),
                     _ => Err(EvalError::TypeError(
                         String::from("Bool"),
                         String::from("&&"),
@@ -226,10 +227,11 @@ fn process_unary_operation(
         }
         UnaryOp::BoolOr() => {
             if let Some((next, ..)) = stack.pop_arg() {
-                match *t {
-                    b @ Term::Bool(true) => {
-                        Ok(Closure::atomic_closure(RichTerm::new(b, pos_op_inh)))
-                    }
+                match &*t {
+                    Term::Bool(true) => Ok(Closure::atomic_closure(RichTerm {
+                        term: t,
+                        pos: pos_op_inh,
+                    })),
                     // FIXME: this does not check that the second argument is actually a boolean.
                     // This means `false || 2` silently evaluates to `2`. This is simpler and more
                     // efficient, but can make debugging harder. In any case, it should be solved
@@ -262,21 +264,26 @@ fn process_unary_operation(
                 ))
             }
         }
-        UnaryOp::Blame() => {
-            if let Term::Lbl(label) = *t {
-                Err(EvalError::BlameError(label, std::mem::take(call_stack)))
-            } else {
+        UnaryOp::Blame() => match_sharedterm! { t, with {
+                Term::Lbl(label) => Err(
+                    EvalError::BlameError(
+                        label,
+                        std::mem::take(call_stack),
+                    )),
+            } else
                 Err(EvalError::TypeError(
                     String::from("Label"),
                     String::from("blame"),
                     arg_pos,
                     RichTerm { term: t, pos },
                 ))
-            }
-        }
+        },
         UnaryOp::Embed(_id) => {
-            if let en @ Term::Enum(_) = *t {
-                Ok(Closure::atomic_closure(RichTerm::new(en, pos_op_inh)))
+            if let Term::Enum(_) = &*t {
+                Ok(Closure::atomic_closure(RichTerm {
+                    term: t,
+                    pos: pos_op_inh,
+                }))
             } else {
                 Err(EvalError::TypeError(
                     String::from("Enum"),
@@ -299,7 +306,7 @@ fn process_unary_operation(
                 None
             };
 
-            if let Term::Enum(en) = *t {
+            if let Term::Enum(en) = &*t {
                 let Closure {
                     body:
                         RichTerm {
@@ -308,13 +315,13 @@ fn process_unary_operation(
                     env: cases_env,
                 } = cases_closure;
 
-                let mut cases = match *cases_term {
+                let mut cases = match cases_term.into_owned() {
                     Term::Record(map, _) => map,
                     _ => panic!("invalid argument for switch"),
                 };
 
                 cases
-                    .remove(&en)
+                    .remove(en)
                     .map(|body| Closure {
                         body,
                         env: cases_env,
@@ -327,7 +334,7 @@ fn process_unary_operation(
                             String::from("switch"),
                             arg_pos,
                             RichTerm {
-                                term: Box::new(Term::Enum(en)),
+                                term: t,
                                 pos,
                             },
                         ))
@@ -342,13 +349,15 @@ fn process_unary_operation(
                 ))
             }
         }
-        UnaryOp::ChangePolarity() => {
-            if let Term::Lbl(mut l) = *t {
-                l.polarity = !l.polarity;
-                Ok(Closure::atomic_closure(RichTerm::new(
-                    Term::Lbl(l),
-                    pos_op_inh,
-                )))
+        UnaryOp::ChangePolarity() => match_sharedterm! {t, with {
+                Term::Lbl(l) => {
+                    let mut l = l;
+                    l.polarity = !l.polarity;
+                    Ok(Closure::atomic_closure(RichTerm::new(
+                        Term::Lbl(l),
+                        pos_op_inh,
+                    )))
+                }
             } else {
                 Err(EvalError::TypeError(
                     String::from("Label"),
@@ -357,9 +366,9 @@ fn process_unary_operation(
                     RichTerm { term: t, pos },
                 ))
             }
-        }
+        },
         UnaryOp::Pol() => {
-            if let Term::Lbl(l) = *t {
+            if let Term::Lbl(l) = &*t {
                 Ok(Closure::atomic_closure(RichTerm::new(
                     Term::Bool(l.polarity),
                     pos_op_inh,
@@ -373,13 +382,15 @@ fn process_unary_operation(
                 ))
             }
         }
-        UnaryOp::GoDom() => {
-            if let Term::Lbl(mut l) = *t {
-                l.path.push(ty_path::Elem::Domain);
-                Ok(Closure::atomic_closure(RichTerm::new(
-                    Term::Lbl(l),
-                    pos_op_inh,
-                )))
+        UnaryOp::GoDom() => match_sharedterm! {t, with {
+                Term::Lbl(l) => {
+                    let mut l = l;
+                    l.path.push(ty_path::Elem::Domain);
+                    Ok(Closure::atomic_closure(RichTerm::new(
+                        Term::Lbl(l),
+                        pos_op_inh,
+                    )))
+                }
             } else {
                 Err(EvalError::TypeError(
                     String::from("Label"),
@@ -388,14 +399,16 @@ fn process_unary_operation(
                     RichTerm { term: t, pos },
                 ))
             }
-        }
-        UnaryOp::GoCodom() => {
-            if let Term::Lbl(mut l) = *t {
-                l.path.push(ty_path::Elem::Codomain);
-                Ok(Closure::atomic_closure(RichTerm::new(
-                    Term::Lbl(l),
-                    pos_op_inh,
-                )))
+        },
+        UnaryOp::GoCodom() => match_sharedterm! {t, with {
+                Term::Lbl(l) => {
+                    let mut l = l;
+                    l.path.push(ty_path::Elem::Codomain);
+                    Ok(Closure::atomic_closure(RichTerm::new(
+                        Term::Lbl(l),
+                        pos_op_inh,
+                    )))
+                }
             } else {
                 Err(EvalError::TypeError(
                     String::from("Label"),
@@ -404,14 +417,16 @@ fn process_unary_operation(
                     RichTerm { term: t, pos },
                 ))
             }
-        }
-        UnaryOp::GoList() => {
-            if let Term::Lbl(mut l) = *t {
-                l.path.push(ty_path::Elem::List);
-                Ok(Closure::atomic_closure(RichTerm::new(
-                    Term::Lbl(l),
-                    pos_op_inh,
-                )))
+        },
+        UnaryOp::GoList() => match_sharedterm! {t, with {
+                Term::Lbl(l) => {
+                    let mut l = l;
+                    l.path.push(ty_path::Elem::List);
+                    Ok(Closure::atomic_closure(RichTerm::new(
+                        Term::Lbl(l),
+                        pos_op_inh,
+                    )))
+                }
             } else {
                 Err(EvalError::TypeError(
                     String::from("Label"),
@@ -420,11 +435,11 @@ fn process_unary_operation(
                     RichTerm { term: t, pos },
                 ))
             }
-        }
+        },
         UnaryOp::Wrap() => {
-            if let Term::Sym(s) = *t {
+            if let Term::Sym(s) = &*t {
                 Ok(Closure::atomic_closure(
-                    mk_fun!("x", Term::Wrapped(s, mk_term::var("x"))).with_pos(pos_op_inh),
+                    mk_fun!("x", Term::Wrapped(*s, mk_term::var("x"))).with_pos(pos_op_inh),
                 ))
             } else {
                 Err(EvalError::TypeError(
@@ -436,19 +451,19 @@ fn process_unary_operation(
             }
         }
         UnaryOp::StaticAccess(id) => {
-            if let Term::Record(mut static_map, attrs) = *t {
-                match static_map.remove(&id) {
+            if let Term::Record(static_map, ..) = &*t {
+                match static_map.get(&id) {
                     Some(e) => {
                         call_stack.enter_field(id, pos, e.pos, pos_op);
-                        Ok(Closure { body: e, env })
+                        Ok(Closure {
+                            body: e.clone(),
+                            env,
+                        })
                     }
                     None => Err(EvalError::FieldMissing(
                         id.label,
                         String::from("(.)"),
-                        RichTerm {
-                            term: Box::new(Term::Record(static_map, attrs)),
-                            pos,
-                        },
+                        RichTerm { term: t, pos },
                         pos_op,
                     )), //TODO include the position of operators on the stack
                 }
@@ -461,15 +476,16 @@ fn process_unary_operation(
                 ))
             }
         }
-        UnaryOp::FieldsOf() => {
-            if let Term::Record(map, _) = *t {
-                let mut fields: Vec<String> = map.keys().map(|id| id.to_string()).collect();
-                fields.sort();
-                let terms = fields.into_iter().map(mk_term::string).collect();
-                Ok(Closure::atomic_closure(RichTerm::new(
-                    Term::List(terms),
-                    pos_op_inh,
-                )))
+        UnaryOp::FieldsOf() => match_sharedterm! {t, with {
+                Term::Record(map, ..) => {
+                    let mut fields: Vec<String> = map.into_keys().map(|id| id.to_string()).collect();
+                    fields.sort();
+                    let terms = fields.into_iter().map(mk_term::string).collect();
+                    Ok(Closure::atomic_closure(RichTerm::new(
+                        Term::List(terms),
+                        pos_op_inh,
+                    )))
+                }
             } else {
                 Err(EvalError::TypeError(
                     String::from("Record"),
@@ -478,19 +494,20 @@ fn process_unary_operation(
                     RichTerm { term: t, pos },
                 ))
             }
-        }
-        UnaryOp::ValuesOf() => {
-            if let Term::Record(map, _) = *t {
-                let mut values: Vec<_> = map.into_iter().collect();
-                // Although it seems that sort_by_key would be easier here, it would actually
-                // require to copy the identifiers because of the lack of HKT. See
-                // https://github.com/rust-lang/rust/issues/34162.
-                values.sort_by(|(id1, _), (id2, _)| id1.cmp(id2));
-                let terms = values.into_iter().map(|(_, t)| t).collect();
-                Ok(Closure {
-                    body: RichTerm::new(Term::List(terms), pos_op_inh),
-                    env,
-                })
+        },
+        UnaryOp::ValuesOf() => match_sharedterm! {t, with {
+                Term::Record(map, ..) => {
+                    let mut values: Vec<_> = map.into_iter().collect();
+                    // Although it seems that sort_by_key would be easier here, it would actually
+                    // require to copy the identifiers because of the lack of HKT. See
+                    // https://github.com/rust-lang/rust/issues/34162.
+                    values.sort_by(|(id1, _), (id2, _)| id1.cmp(id2));
+                    let terms = values.into_iter().map(|(_, t)| t).collect();
+                    Ok(Closure {
+                        body: RichTerm::new(Term::List(terms), pos_op_inh),
+                        env,
+                    })
+                }
             } else {
                 Err(EvalError::TypeError(
                     String::from("Record"),
@@ -499,38 +516,40 @@ fn process_unary_operation(
                     RichTerm { term: t, pos },
                 ))
             }
-        }
+        },
         UnaryOp::ListMap() => {
             let (f, ..) = stack
                 .pop_arg()
                 .ok_or_else(|| EvalError::NotEnoughArgs(2, String::from("map"), pos_op))?;
+            match_sharedterm! {t, with {
+                    Term::List(ts) => {
+                        let mut shared_env = Environment::new();
+                        let f_as_var = f.body.closurize(&mut env, f.env);
+
+                        // List elements are closurized to preserve lazyness of data structures. It
+                        // maintains the invariant that any data structure only contain thunks (that is,
+                        // currently, variables).
+                        let ts = ts
+                            .into_iter()
+                            .map(|t| {
+                                RichTerm::new(Term::App(f_as_var.clone(), t), pos_op_inh)
+                                    .closurize(&mut shared_env, env.clone())
+                            })
+                            .collect();
 
-            if let Term::List(ts) = *t {
-                let mut shared_env = Environment::new();
-                let f_as_var = f.body.closurize(&mut env, f.env);
-
-                // List elements are closurized to preserve lazyness of data structures. It
-                // maintains the invariant that any data structure only contain thunks (that is,
-                // currently, variables).
-                let ts = ts
-                    .into_iter()
-                    .map(|t| {
-                        RichTerm::new(Term::App(f_as_var.clone(), t), pos_op_inh)
-                            .closurize(&mut shared_env, env.clone())
-                    })
-                    .collect();
-
-                Ok(Closure {
-                    body: RichTerm::new(Term::List(ts), pos_op_inh),
-                    env: shared_env,
-                })
-            } else {
-                Err(EvalError::TypeError(
-                    String::from("List"),
-                    String::from("map, 2nd argument"),
-                    arg_pos,
-                    RichTerm { term: t, pos },
-                ))
+                        Ok(Closure {
+                            body: RichTerm::new(Term::List(ts), pos_op_inh),
+                            env: shared_env,
+                        })
+                    }
+                } else {
+                    Err(EvalError::TypeError(
+                        String::from("List"),
+                        String::from("map, 2nd argument"),
+                        arg_pos,
+                        RichTerm { term: t, pos },
+                    ))
+                }
             }
         }
         UnaryOp::ListGen() => {
@@ -581,36 +600,39 @@ fn process_unary_operation(
                 .pop_arg()
                 .ok_or_else(|| EvalError::NotEnoughArgs(2, String::from("recordMap"), pos_op))?;
 
-            if let Term::Record(rec, attr) = *t {
-                let mut shared_env = Environment::new();
-                let f_as_var = f.body.closurize(&mut env, f.env);
-
-                // As for `ListMap` (see above), we closurize the content of fields
-                let rec = rec
-                    .into_iter()
-                    .map(|e| {
-                        let (id, t) = e;
-                        let pos = t.pos.into_inherited();
-                        (
-                            id.clone(),
-                            mk_app!(f_as_var.clone(), mk_term::string(id.label), t)
-                                .closurize(&mut shared_env, env.clone())
-                                .with_pos(pos),
-                        )
-                    })
-                    .collect();
+            match_sharedterm! {t, with {
+                    Term::Record(rec, attr) => {
+                        let mut shared_env = Environment::new();
+                        let f_as_var = f.body.closurize(&mut env, f.env);
+
+                        // As for `ListMap` (see above), we closurize the content of fields
+                        let rec = rec
+                            .into_iter()
+                            .map(|e| {
+                                let (id, t) = e;
+                                let pos = t.pos.into_inherited();
+                                (
+                                    id.clone(),
+                                    mk_app!(f_as_var.clone(), mk_term::string(id.label), t)
+                                        .closurize(&mut shared_env, env.clone())
+                                        .with_pos(pos),
+                                )
+                            })
+                            .collect();
 
-                Ok(Closure {
-                    body: RichTerm::new(Term::Record(rec, attr), pos_op_inh),
-                    env: shared_env,
-                })
-            } else {
-                Err(EvalError::TypeError(
-                    String::from("Record"),
-                    String::from("map on record"),
-                    arg_pos,
-                    RichTerm { term: t, pos },
-                ))
+                        Ok(Closure {
+                            body: RichTerm::new(Term::Record(rec, attr), pos_op_inh),
+                            env: shared_env,
+                        })
+                    }
+                } else {
+                    Err(EvalError::TypeError(
+                        String::from("Record"),
+                        String::from("map on record"),
+                        arg_pos,
+                        RichTerm { term: t, pos },
+                    ))
+                }
             }
         }
         UnaryOp::Seq() => {
@@ -641,7 +663,7 @@ fn process_unary_operation(
                 Closure { body, env }
             }
 
-            match *t {
+            match t.into_owned() {
                 Term::Record(map, _) if !map.is_empty() => {
                     let terms = map.into_iter().map(|(_, t)| t);
                     Ok(seq_terms(terms, env, pos_op))
@@ -657,10 +679,12 @@ fn process_unary_operation(
             }
         }
         UnaryOp::ListHead() => {
-            if let Term::List(ts) = *t {
-                let mut ts_it = ts.into_iter();
-                if let Some(head) = ts_it.next() {
-                    Ok(Closure { body: head, env })
+            if let Term::List(ts) = &*t {
+                if let Some(head) = ts.first() {
+                    Ok(Closure {
+                        body: head.clone(),
+                        env,
+                    })
                 } else {
                     Err(EvalError::Other(String::from("head: empty list"), pos_op))
                 }
@@ -673,28 +697,29 @@ fn process_unary_operation(
                 ))
             }
         }
-        UnaryOp::ListTail() => {
-            if let Term::List(ts) = *t {
-                let mut ts_it = ts.into_iter();
-                if ts_it.next().is_some() {
-                    Ok(Closure {
-                        body: RichTerm::new(Term::List(ts_it.collect()), pos_op_inh),
-                        env,
-                    })
+        UnaryOp::ListTail() => match_sharedterm! {t, with {
+                    Term::List(ts) => {
+                        let mut ts_it = ts.into_iter();
+                        if ts_it.next().is_some() {
+                            Ok(Closure {
+                                body: RichTerm::new(Term::List(ts_it.collect()), pos_op_inh),
+                                env,
+                            })
+                        } else {
+                            Err(EvalError::Other(String::from("tail: empty list"), pos_op))
+                        }
+                    }
                 } else {
-                    Err(EvalError::Other(String::from("tail: empty list"), pos_op))
+                    Err(EvalError::TypeError(
+                        String::from("List"),
+                        String::from("tail"),
+                        arg_pos,
+                        RichTerm { term: t, pos },
+                    ))
                 }
-            } else {
-                Err(EvalError::TypeError(
-                    String::from("List"),
-                    String::from("tail"),
-                    arg_pos,
-                    RichTerm { term: t, pos },
-                ))
-            }
-        }
+        },
         UnaryOp::ListLength() => {
-            if let Term::List(ts) = *t {
+            if let Term::List(ts) = &*t {
                 // A num does not have any free variable so we can drop the environment
                 Ok(Closure {
                     body: RichTerm::new(Term::Num(ts.len() as f64), pos_op_inh),
@@ -712,14 +737,14 @@ fn process_unary_operation(
         UnaryOp::ChunksConcat() => {
             let (mut acc, indent, env_chunks) = stack.pop_str_acc().unwrap();
 
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 let s = if indent != 0 {
                     let indent_str: String = std::iter::once('\n')
                         .chain((0..indent).map(|_| ' '))
                         .collect();
                     s.replace("\n", &indent_str)
                 } else {
-                    s
+                    s.clone()
                 };
 
                 acc.push_str(&s);
@@ -757,7 +782,7 @@ fn process_unary_operation(
             }
         }
         UnaryOp::StrTrim() => {
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 Ok(Closure::atomic_closure(RichTerm::new(
                     Term::Str(String::from(s.trim())),
                     pos_op_inh,
@@ -772,7 +797,7 @@ fn process_unary_operation(
             }
         }
         UnaryOp::StrChars() => {
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 let ts = s
                     .chars()
                     .map(|c| RichTerm::from(Term::Str(c.to_string())))
@@ -791,7 +816,7 @@ fn process_unary_operation(
             }
         }
         UnaryOp::CharCode() => {
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 if s.len() == 1 {
                     let code = (s.chars().next().unwrap() as u32) as f64;
                     Ok(Closure::atomic_closure(RichTerm::new(
@@ -840,7 +865,7 @@ fn process_unary_operation(
             }
         }
         UnaryOp::StrUppercase() => {
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 Ok(Closure::atomic_closure(RichTerm::new(
                     Term::Str(s.to_uppercase()),
                     pos_op_inh,
@@ -855,7 +880,7 @@ fn process_unary_operation(
             }
         }
         UnaryOp::StrLowercase() => {
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 Ok(Closure::atomic_closure(RichTerm::new(
                     Term::Str(s.to_lowercase()),
                     pos_op_inh,
@@ -870,7 +895,7 @@ fn process_unary_operation(
             }
         }
         UnaryOp::StrLength() => {
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 Ok(Closure::atomic_closure(RichTerm::new(
                     Term::Num(s.len() as f64),
                     pos_op_inh,
@@ -885,9 +910,9 @@ fn process_unary_operation(
             }
         }
         UnaryOp::ToStr() => {
-            let result = match *t {
+            let result = match &*t {
                 Term::Num(n) => Ok(Term::Str(n.to_string())),
-                Term::Str(s) => Ok(Term::Str(s)),
+                Term::Str(s) => Ok(Term::Str(s.clone())),
                 Term::Bool(b) => Ok(Term::Str(b.to_string())),
                 Term::Enum(id) => Ok(Term::Str(id.to_string())),
                 t => Err(EvalError::Other(
@@ -901,7 +926,7 @@ fn process_unary_operation(
             Ok(Closure::atomic_closure(RichTerm::new(result, pos_op_inh)))
         }
         UnaryOp::NumFromStr() => {
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 let n = s.parse::<f64>().map_err(|_| {
                     EvalError::Other(format!("numFrom: invalid num literal `{}`", s), pos)
                 })?;
@@ -919,7 +944,7 @@ fn process_unary_operation(
             }
         }
         UnaryOp::EnumFromStr() => {
-            if let Term::Str(s) = *t {
+            if let Term::Str(s) = &*t {
                 Ok(Closure::atomic_closure(RichTerm::new(
                     Term::Enum(s.into()),
                     pos_op_inh,
@@ -1152,10 +1177,11 @@ fn process_binary_operation(
             }
         }
         BinaryOp::StrConcat() => {
-            if let Term::Str(s1) = *t1 {
-                if let Term::Str(s2) = *t2 {
+            if let Term::Str(s1) = &*t1 {
+                if let Term::Str(s2) = &*t2 {
+                    let ss: [&str; 2] = [s1, s2];
                     Ok(Closure::atomic_closure(RichTerm::new(
-                        Term::Str(s1 + &s2),
+                        Term::Str(ss.concat()),
                         pos_op_inh,
                     )))
                 } else {
@@ -1182,12 +1208,13 @@ fn process_binary_operation(
             }
         }
         BinaryOp::Assume() => {
-            if let Term::Lbl(mut l) = *t2 {
+            if let Term::Lbl(l) = &*t2 {
                 // Track the contract argument for better error reporting, and push back the label
                 // on the stack, so that it becomes the first argument of the contract.
                 let thunk = stack
                     .track_arg()
                     .ok_or_else(|| EvalError::NotEnoughArgs(3, String::from("assume"), pos_op))?;
+                let mut l = l.clone();
                 l.arg_pos = thunk.borrow().body.pos;
                 l.arg_thunk = Some(thunk);
 
@@ -1251,12 +1278,12 @@ fn process_binary_operation(
             }
         }
         BinaryOp::Unwrap() => {
-            if let Term::Sym(s1) = *t1 {
+            if let Term::Sym(s1) = &*t1 {
                 // Return a function that either behaves like the identity or
                 // const unwrapped_term
 
-                Ok(if let Term::Wrapped(s2, t) = *t2 {
-                    if s1 == s2 {
+                Ok(if let Term::Wrapped(s2, t) = t2.into_owned() {
+                    if *s1 == s2 {
                         Closure {
                             body: mk_fun!("-invld", t),
                             env: env2,
@@ -1279,25 +1306,28 @@ fn process_binary_operation(
                 ))
             }
         }
-        BinaryOp::Tag() => {
-            if let Term::Str(s) = *t1 {
-                if let Term::Lbl(mut l) = *t2 {
-                    l.tag = s;
-                    Ok(Closure::atomic_closure(RichTerm::new(
-                        Term::Lbl(l),
-                        pos_op_inh,
-                    )))
-                } else {
-                    Err(EvalError::TypeError(
-                        String::from("Label"),
-                        String::from("tag, 2nd argument"),
-                        snd_pos,
-                        RichTerm {
-                            term: t2,
-                            pos: pos2,
-                        },
-                    ))
-                }
+        BinaryOp::Tag() => match_sharedterm! {t1, with {
+                Term::Str(s) => match_sharedterm!{t2, with {
+                            Term::Lbl(l) => {
+                                let mut l = l;
+                                l.tag = s;
+                                Ok(Closure::atomic_closure(RichTerm::new(
+                                    Term::Lbl(l),
+                                    pos_op_inh,
+                                )))
+                            }
+                        } else {
+                            Err(EvalError::TypeError(
+                                String::from("Label"),
+                                String::from("tag, 2nd argument"),
+                                snd_pos,
+                                RichTerm {
+                                    term: t2,
+                                    pos: pos2,
+                                },
+                            ))
+                        }
+                    }
             } else {
                 Err(EvalError::TypeError(
                     String::from("Str"),
@@ -1309,7 +1339,7 @@ fn process_binary_operation(
                     },
                 ))
             }
-        }
+        },
         BinaryOp::Eq() => {
             let mut env = Environment::new();
 
@@ -1481,25 +1511,28 @@ fn process_binary_operation(
                 ))
             }
         }
-        BinaryOp::GoField() => {
-            if let Term::Str(field) = *t1 {
-                if let Term::Lbl(mut l) = *t2 {
-                    l.path.push(ty_path::Elem::Field(Ident::from(field)));
-                    Ok(Closure::atomic_closure(RichTerm::new(
-                        Term::Lbl(l),
-                        pos_op_inh,
-                    )))
-                } else {
-                    Err(EvalError::TypeError(
-                        String::from("Label"),
-                        String::from("goField, 2nd argument"),
-                        snd_pos,
-                        RichTerm {
-                            term: t2,
-                            pos: pos2,
-                        },
-                    ))
-                }
+        BinaryOp::GoField() => match_sharedterm! {t1, with {
+                Term::Str(field) => match_sharedterm! {t2, with {
+                        Term::Lbl(l) => {
+                            let mut l = l;
+                            l.path.push(ty_path::Elem::Field(Ident::from(field)));
+                            Ok(Closure::atomic_closure(RichTerm::new(
+                                Term::Lbl(l),
+                                pos_op_inh,
+                            )))
+                        }
+                    } else {
+                        Err(EvalError::TypeError(
+                            String::from("Label"),
+                            String::from("goField, 2nd argument"),
+                            snd_pos,
+                            RichTerm {
+                                term: t2,
+                                pos: pos2,
+                            },
+                        ))
+                    }
+                },
             } else {
                 Err(EvalError::TypeError(
                     String::from("Str"),
@@ -1511,37 +1544,39 @@ fn process_binary_operation(
                     },
                 ))
             }
-        }
-
-        BinaryOp::DynAccess() => {
-            if let Term::Str(id_str) = *t1 {
-                if let Term::Record(mut static_map, attrs) = *t2 {
-                    let ident = Ident::from(&id_str);
-                    match static_map.remove(&ident) {
-                        Some(e) => {
-                            call_stack.enter_field(ident, pos2, e.pos, pos_op);
-                            Ok(Closure { body: e, env: env2 })
+        },
+        BinaryOp::DynAccess() => match_sharedterm! {t1, with {
+                Term::Str(id) => {
+                    if let Term::Record(static_map, _attrs) = &*t2 {
+                        match static_map.get(&Ident::from(&id)) {
+                            Some(e) => {
+                                call_stack.enter_field(Ident::from(id), pos2, e.pos, pos_op);
+                                Ok(Closure {
+                                    body: e.clone(),
+                                    env: env2,
+                                })
+                            }
+                            None => Err(EvalError::FieldMissing(
+                                id,
+                                String::from("(.$)"),
+                                RichTerm {
+                                    term: t2,
+                                    pos: pos2,
+                                },
+                                pos_op,
+                            )),
                         }
-                        None => Err(EvalError::FieldMissing(
-                            id_str,
-                            String::from("(.$)"),
+                    } else {
+                        Err(EvalError::TypeError(
+                            String::from("Record"),
+                            String::from(".$"),
+                            snd_pos,
                             RichTerm {
-                                term: Box::new(Term::Record(static_map, attrs)),
+                                term: t2,
                                 pos: pos2,
                             },
-                            pos_op,
-                        )),
+                        ))
                     }
-                } else {
-                    Err(EvalError::TypeError(
-                        String::from("Record"),
-                        String::from(".$"),
-                        snd_pos,
-                        RichTerm {
-                            term: t2,
-                            pos: pos2,
-                        },
-                    ))
                 }
             } else {
                 Err(EvalError::TypeError(
@@ -1554,32 +1589,36 @@ fn process_binary_operation(
                     },
                 ))
             }
-        }
+        },
         BinaryOp::DynExtend() => {
             let (clos, _) = stack
                 .pop_arg()
                 .ok_or_else(|| EvalError::NotEnoughArgs(3, String::from("$[ .. ]"), pos_op))?;
 
-            if let Term::Str(id) = *t1 {
-                if let Term::Record(mut static_map, attrs) = *t2 {
-                    let as_var = clos.body.closurize(&mut env2, clos.env);
-                    match static_map.insert(Ident::from(&id), as_var) {
-                        Some(_) => Err(EvalError::Other(format!("$[ .. ]: tried to extend record with the field {}, but it already exists", id), pos_op)),
-                        None => Ok(Closure {
-                            body: Term::Record(static_map, attrs).into(),
-                            env: env2,
-                        }),
+            if let Term::Str(id) = &*t1 {
+                match_sharedterm! {t2, with {
+                        Term::Record(static_map, attrs) => {
+                            let mut static_map = static_map;
+                            let as_var = clos.body.closurize(&mut env2, clos.env);
+                            match static_map.insert(Ident::from(id), as_var) {
+                                Some(_) => Err(EvalError::Other(format!("$[ .. ]: tried to extend record with the field {}, but it already exists", id), pos_op)),
+                                None => Ok(Closure {
+                                    body: Term::Record(static_map, attrs).into(),
+                                    env: env2,
+                                }),
+                            }
+                        }
+                    } else {
+                        Err(EvalError::TypeError(
+                            String::from("Record"),
+                            String::from("$[ .. ]"),
+                            snd_pos,
+                            RichTerm {
+                                term: t2,
+                                pos: pos2,
+                            },
+                        ))
                     }
-                } else {
-                    Err(EvalError::TypeError(
-                        String::from("Record"),
-                        String::from("$[ .. ]"),
-                        snd_pos,
-                        RichTerm {
-                            term: t2,
-                            pos: pos2,
-                        },
-                    ))
                 }
             } else {
                 Err(EvalError::TypeError(
@@ -1593,35 +1632,38 @@ fn process_binary_operation(
                 ))
             }
         }
-        BinaryOp::DynRemove() => {
-            if let Term::Str(id) = *t1 {
-                if let Term::Record(mut static_map, attrs) = *t2 {
-                    match static_map.remove(&Ident::from(&id)) {
-                        None => Err(EvalError::FieldMissing(
-                            id,
-                            String::from("(-$)"),
+        BinaryOp::DynRemove() => match_sharedterm! {t1, with {
+                Term::Str(id) => match_sharedterm! {t2, with {
+                        Term::Record(static_map, attrs) => {
+                            let mut static_map = static_map;
+                            match static_map.remove(&Ident::from(&id)) {
+                                None => Err(EvalError::FieldMissing(
+                                    id,
+                                    String::from("(-$)"),
+                                    RichTerm::new(
+                                        Term::Record(static_map, attrs),
+                                        pos2,
+                                    ),
+                                    pos_op,
+                                )),
+                                Some(_) => Ok(Closure {
+                                    body: RichTerm::new(Term::Record(static_map, attrs), pos_op_inh),
+                                    env: env2,
+                                }),
+                            }
+                        }
+                    } else {
+                        Err(EvalError::TypeError(
+                            String::from("Record"),
+                            String::from("-$"),
+                            snd_pos,
                             RichTerm {
-                                term: Box::new(Term::Record(static_map, attrs)),
+                                term: t2,
                                 pos: pos2,
                             },
-                            pos_op,
-                        )),
-                        Some(_) => Ok(Closure {
-                            body: RichTerm::new(Term::Record(static_map, attrs), pos_op_inh),
-                            env: env2,
-                        }),
+                        ))
                     }
-                } else {
-                    Err(EvalError::TypeError(
-                        String::from("Record"),
-                        String::from("-$"),
-                        snd_pos,
-                        RichTerm {
-                            term: t2,
-                            pos: pos2,
-                        },
-                    ))
-                }
+                },
             } else {
                 Err(EvalError::TypeError(
                     String::from("Str"),
@@ -1633,24 +1675,25 @@ fn process_binary_operation(
                     },
                 ))
             }
-        }
-        BinaryOp::HasField() => {
-            if let Term::Str(id) = *t1 {
-                if let Term::Record(static_map, _) = *t2 {
-                    Ok(Closure::atomic_closure(RichTerm::new(
-                        Term::Bool(static_map.contains_key(&Ident::from(id))),
-                        pos_op_inh,
-                    )))
-                } else {
-                    Err(EvalError::TypeError(
-                        String::from("Record"),
-                        String::from("hasField, 2nd argument"),
-                        snd_pos,
-                        RichTerm {
-                            term: t2,
-                            pos: pos2,
-                        },
-                    ))
+        },
+        BinaryOp::HasField() => match_sharedterm! {t1, with {
+                Term::Str(id) => {
+                    if let Term::Record(static_map, _) = &*t2 {
+                        Ok(Closure::atomic_closure(RichTerm::new(
+                            Term::Bool(static_map.contains_key(&Ident::from(id))),
+                            pos_op_inh,
+                        )))
+                    } else {
+                        Err(EvalError::TypeError(
+                            String::from("Record"),
+                            String::from("hasField, 2nd argument"),
+                            snd_pos,
+                            RichTerm {
+                                term: t2,
+                                pos: pos2,
+                            },
+                        ))
+                    }
                 }
             } else {
                 Err(EvalError::TypeError(
@@ -1663,69 +1706,80 @@ fn process_binary_operation(
                     },
                 ))
             }
-        }
-        BinaryOp::ListConcat() => match (*t1, *t2) {
-            (Term::List(ts1), Term::List(ts2)) => {
-                let mut env = Environment::new();
-                let mut ts: Vec<RichTerm> = ts1
-                    .into_iter()
-                    .map(|t| t.closurize(&mut env, env1.clone()))
-                    .collect();
-                ts.extend(ts2.into_iter().map(|t| t.closurize(&mut env, env2.clone())));
+        },
+        BinaryOp::ListConcat() => match_sharedterm! {t1, with {
+                Term::List(ts1) => match_sharedterm! {t2, with {
+                        Term::List(ts2) => {
+                            let mut env = Environment::new();
+                            let mut ts: Vec<RichTerm> = Vec::with_capacity(ts1.len() + ts2.len());
+                            ts.extend(
+                                ts1.into_iter()
+                                    .map(|t| t.closurize(&mut env, env1.clone())),
+                            );
+                            ts.extend(
+                                ts2.into_iter()
+                                    .map(|t| t.closurize(&mut env, env2.clone())),
+                            );
+
+                            Ok(Closure {
+                                body: RichTerm::new(Term::List(ts), pos_op_inh),
+                                env,
+                            })
+                        }
+                    } else {
+                        Err(EvalError::TypeError(
+                            String::from("List"),
+                            String::from("@, 2nd operand"),
+                            snd_pos,
+                            RichTerm {
+                                term: t2,
+                                pos: pos2,
+                            },
+                        ))
 
-                Ok(Closure {
-                    body: RichTerm::new(Term::List(ts), pos_op_inh),
-                    env,
-                })
-            }
-            (Term::List(_), t2) => Err(EvalError::TypeError(
-                String::from("List"),
-                String::from("@, 2nd operand"),
-                snd_pos,
-                RichTerm {
-                    term: Box::new(t2),
-                    pos: pos2,
-                },
-            )),
-            (t1, _) => Err(EvalError::TypeError(
-                String::from("List"),
-                String::from("@, 1st operand"),
-                fst_pos,
-                RichTerm {
-                    term: Box::new(t1),
-                    pos: pos1,
+                    }
                 },
-            )),
+            } else {
+                Err(EvalError::TypeError(
+                    String::from("List"),
+                    String::from("@, 1st operand"),
+                    fst_pos,
+                    RichTerm {
+                        term: t1,
+                        pos: pos1,
+                    },
+                ))
+            }
         },
-        BinaryOp::ListElemAt() => match (*t1, *t2) {
-            (Term::List(mut ts), Term::Num(n)) => {
-                let n_int = n as usize;
+        BinaryOp::ListElemAt() => match (&*t1, &*t2) {
+            (Term::List(ts), Term::Num(n)) => {
+                let n_int = *n as usize;
                 if n.fract() != 0.0 {
                     Err(EvalError::Other(format!("elemAt: expected the 2nd agument to be an integer, got the floating-point value {}", n), pos_op))
-                } else if n < 0.0 || n_int >= ts.len() {
+                } else if *n < 0.0 || n_int >= ts.len() {
                     Err(EvalError::Other(format!("elemAt: index out of bounds. Expected a value between 0 and {}, got {}", ts.len(), n), pos_op))
                 } else {
                     Ok(Closure {
-                        body: ts.swap_remove(n_int),
+                        body: ts[n_int].clone(),
                         env: env1,
                     })
                 }
             }
-            (Term::List(_), t2) => Err(EvalError::TypeError(
+            (Term::List(_), _) => Err(EvalError::TypeError(
                 String::from("Num"),
                 String::from("elemAt, 2nd argument"),
                 snd_pos,
                 RichTerm {
-                    term: Box::new(t2),
+                    term: t2,
                     pos: pos2,
                 },
             )),
-            (t1, _) => Err(EvalError::TypeError(
+            (_, _) => Err(EvalError::TypeError(
                 String::from("List"),
                 String::from("elemAt, 1st argument"),
                 fst_pos,
                 RichTerm {
-                    term: Box::new(t1),
+                    term: t1,
                     pos: pos1,
                 },
             )),
@@ -1758,9 +1812,9 @@ fn process_binary_operation(
                 ))
             };
 
-            if let Term::Enum(ref id) = t1.as_ref() {
-                if let Term::Str(s) = *t2 {
-                    let result = match id.to_string().as_str() {
+            if let Term::Enum(id) = &*t1 {
+                if let Term::Str(s) = &*t2 {
+                    let result = match id.as_ref() {
                         "Md5" => {
                             let mut hasher = md5::Md5::new();
                             hasher.update(s);
@@ -1857,24 +1911,24 @@ fn process_binary_operation(
                 ))
             };
 
-            if let Term::Enum(ref id) = t1.as_ref() {
-                if let Term::Str(s) = *t2 {
-                    let rt: RichTerm = match id.to_string().as_str() {
-                        "Json" => serde_json::from_str(&s).map_err(|err| {
+            if let Term::Enum(id) = &*t1 {
+                if let Term::Str(s) = &*t2 {
+                    let rt: RichTerm = match id.as_ref() {
+                        "Json" => serde_json::from_str(s).map_err(|err| {
                             EvalError::DeserializationError(
                                 String::from("json"),
                                 format!("{}", err),
                                 pos_op,
                             )
                         })?,
-                        "Yaml" => serde_yaml::from_str(&s).map_err(|err| {
+                        "Yaml" => serde_yaml::from_str(s).map_err(|err| {
                             EvalError::DeserializationError(
                                 String::from("yaml"),
                                 format!("{}", err),
                                 pos_op,
                             )
                         })?,
-                        "Toml" => toml::from_str(&s).map_err(|err| {
+                        "Toml" => toml::from_str(s).map_err(|err| {
                             EvalError::DeserializationError(
                                 String::from("toml"),
                                 format!("{}", err),
@@ -1900,10 +1954,10 @@ fn process_binary_operation(
                 mk_err_fst(t1)
             }
         }
-        BinaryOp::StrSplit() => match (*t1, *t2) {
+        BinaryOp::StrSplit() => match (&*t1, &*t2) {
             (Term::Str(s1), Term::Str(s2)) => {
                 let list: Vec<RichTerm> = s1
-                    .split(&s2)
+                    .split(s2)
                     .map(|s| Term::Str(String::from(s)).into())
                     .collect();
                 Ok(Closure::atomic_closure(RichTerm::new(
@@ -1911,84 +1965,84 @@ fn process_binary_operation(
                     pos_op_inh,
                 )))
             }
-            (Term::Str(_), t2) => Err(EvalError::TypeError(
+            (Term::Str(_), _) => Err(EvalError::TypeError(
                 String::from("Str"),
                 String::from("strSplit, 2nd argument"),
                 snd_pos,
                 RichTerm {
-                    term: Box::new(t2),
+                    term: t2,
                     pos: pos2,
                 },
             )),
-            (t1, _) => Err(EvalError::TypeError(
+            (_, _) => Err(EvalError::TypeError(
                 String::from("Str"),
                 String::from("strSplit, 1st argument"),
                 fst_pos,
                 RichTerm {
-                    term: Box::new(t1),
+                    term: t1,
                     pos: pos1,
                 },
             )),
         },
-        BinaryOp::StrContains() => match (*t1, *t2) {
+        BinaryOp::StrContains() => match (&*t1, &*t2) {
             (Term::Str(s1), Term::Str(s2)) => Ok(Closure::atomic_closure(RichTerm::new(
-                Term::Bool(s1.contains(&s2)),
+                Term::Bool(s1.contains(s2)),
                 pos_op_inh,
             ))),
-            (Term::Str(_), t2) => Err(EvalError::TypeError(
+            (Term::Str(_), _) => Err(EvalError::TypeError(
                 String::from("Str"),
                 String::from("strContains, 2nd argument"),
                 snd_pos,
                 RichTerm {
-                    term: Box::new(t2),
+                    term: t2,
                     pos: pos2,
                 },
             )),
-            (t1, _) => Err(EvalError::TypeError(
+            (_, _) => Err(EvalError::TypeError(
                 String::from("Str"),
                 String::from("strContains, 1st argument"),
                 fst_pos,
                 RichTerm {
-                    term: Box::new(t1),
+                    term: t1,
                     pos: pos1,
                 },
             )),
         },
-        BinaryOp::StrIsMatch() => match (*t1, *t2) {
+        BinaryOp::StrIsMatch() => match (&*t1, &*t2) {
             (Term::Str(s1), Term::Str(s2)) => {
-                let re = regex::Regex::new(&s2)
+                let re = regex::Regex::new(s2)
                     .map_err(|err| EvalError::Other(err.to_string(), pos_op))?;
 
                 Ok(Closure::atomic_closure(RichTerm::new(
-                    Term::Bool(re.is_match(&s1)),
+                    Term::Bool(re.is_match(s1)),
                     pos_op_inh,
                 )))
             }
-            (Term::Str(_), t2) => Err(EvalError::TypeError(
+            (Term::Str(_), _) => Err(EvalError::TypeError(
                 String::from("Str"),
                 String::from("strIsMatch, 2nd argument"),
                 snd_pos,
                 RichTerm {
-                    term: Box::new(t2),
+                    term: t2,
                     pos: pos2,
                 },
             )),
-            (t1, _) => Err(EvalError::TypeError(
+            (_, _) => Err(EvalError::TypeError(
                 String::from("Str"),
                 String::from("strIsMatch, 1st argument"),
                 fst_pos,
                 RichTerm {
-                    term: Box::new(t1),
+                    term: t1,
                     pos: pos1,
                 },
             )),
         },
         BinaryOp::StrMatch() => {
-            match (*t1, *t2) {
+            match (&*t1, &*t2) {
                 (Term::Str(s1), Term::Str(s2)) => {
-                    let re = regex::Regex::new(&s2)
+                    let re = regex::Regex::new(s2)
                         .map_err(|err| EvalError::Other(err.to_string(), pos_op))?;
-                    let capt = re.captures(&s1);
+                    let capt = re.captures(s1);
 
                     let result = if let Some(capt) = capt {
                         let first_match = capt.get(0).unwrap();
@@ -2017,21 +2071,21 @@ fn process_binary_operation(
 
                     Ok(Closure::atomic_closure(result))
                 }
-                (Term::Str(_), t2) => Err(EvalError::TypeError(
+                (Term::Str(_), _) => Err(EvalError::TypeError(
                     String::from("Str"),
                     String::from("strMatch, 2nd argument"),
                     snd_pos,
                     RichTerm {
-                        term: Box::new(t2),
+                        term: t2,
                         pos: pos2,
                     },
                 )),
-                (t1, _) => Err(EvalError::TypeError(
+                (_, _) => Err(EvalError::TypeError(
                     String::from("Str"),
                     String::from("strMatch, 1st argument"),
                     fst_pos,
                     RichTerm {
-                        term: Box::new(t1),
+                        term: t1,
                         pos: pos1,
                     },
                 )),
@@ -2064,15 +2118,15 @@ fn process_nary_operation(
             let (thd, pos3, thd_pos) = args_wo_env.next().unwrap();
             debug_assert!(args_wo_env.next().is_none());
 
-            match (*fst, *snd, *thd) {
+            match (&*fst, &*snd, &*thd) {
                 (Term::Str(s), Term::Str(from), Term::Str(to)) => {
                     let result = if let NAryOp::StrReplace() = n_op {
-                        str::replace(&s, &from, &to)
+                        str::replace(s, from, to)
                     } else {
-                        let re = regex::Regex::new(&from)
+                        let re = regex::Regex::new(from)
                             .map_err(|err| EvalError::Other(err.to_string(), pos_op))?;
 
-                        re.replace_all(&s, to.as_str()).into_owned()
+                        re.replace_all(s, to.as_str()).into_owned()
                     };
 
                     Ok(Closure::atomic_closure(RichTerm::new(
@@ -2080,23 +2134,32 @@ fn process_nary_operation(
                         pos_op_inh,
                     )))
                 }
-                (Term::Str(_), Term::Str(_), t3) => Err(EvalError::TypeError(
+                (Term::Str(_), Term::Str(_), _) => Err(EvalError::TypeError(
                     String::from("Str"),
                     format!("{}, 3rd argument", n_op),
                     thd_pos,
-                    RichTerm::new(t3, pos3),
+                    RichTerm {
+                        term: thd,
+                        pos: pos3,
+                    },
                 )),
-                (Term::Str(_), t2, _) => Err(EvalError::TypeError(
+                (Term::Str(_), _, _) => Err(EvalError::TypeError(
                     String::from("Str"),
                     format!("{}, 2nd argument", n_op),
                     snd_pos,
-                    RichTerm::new(t2, pos2),
+                    RichTerm {
+                        term: snd,
+                        pos: pos2,
+                    },
                 )),
-                (t1, _, _) => Err(EvalError::TypeError(
+                (_, _, _) => Err(EvalError::TypeError(
                     String::from("Str"),
                     format!("{}, 1st argument", n_op),
                     fst_pos,
-                    RichTerm::new(t1, pos1),
+                    RichTerm {
+                        term: fst,
+                        pos: pos1,
+                    },
                 )),
             }
         }
@@ -2109,43 +2172,52 @@ fn process_nary_operation(
             let (thd, pos3, thd_pos) = args_wo_env.next().unwrap();
             debug_assert!(args_wo_env.next().is_none());
 
-            match (*fst, *snd, *thd) {
+            match (&*fst, &*snd, &*thd) {
                 (Term::Str(s), Term::Num(start), Term::Num(end)) => {
-                    let start_int = start as usize;
-                    let end_int = end as usize;
+                    let start_int = *start as usize;
+                    let end_int = *end as usize;
 
                     if start.fract() != 0.0 {
                         Err(EvalError::Other(format!("substring: expected the 2nd agument (start) to be an integer, got the floating-point value {}", start), pos_op))
-                    } else if start < 0.0 || start_int >= s.len() {
+                    } else if !s.is_char_boundary(start_int) {
                         Err(EvalError::Other(format!("substring: index out of bounds. Expected the 2nd argument (start) to be between 0 and {}, got {}", s.len(), start), pos_op))
                     } else if end.fract() != 0.0 {
                         Err(EvalError::Other(format!("substring: expected the 3nd argument (end) to be an integer, got the floating-point value {}", end), pos_op))
-                    } else if end <= start || end_int >= s.len() {
+                    } else if end <= start || !s.is_char_boundary(end_int) {
                         Err(EvalError::Other(format!("substring: index out of bounds. Expected the 3rd argument (end) to be between {} and {}, got {}", start+1., s.len(), end), pos_op))
                     } else {
                         Ok(Closure::atomic_closure(RichTerm::new(
-                            Term::Str(String::from(&s[start_int..end_int])),
+                            Term::Str(s[start_int..end_int].to_owned()),
                             pos_op_inh,
                         )))
                     }
                 }
-                (Term::Str(_), Term::Str(_), t3) => Err(EvalError::TypeError(
+                (Term::Str(_), Term::Num(_), _) => Err(EvalError::TypeError(
                     String::from("Str"),
                     String::from("strReplace, 3rd argument"),
                     thd_pos,
-                    RichTerm::new(t3, pos3),
+                    RichTerm {
+                        term: thd,
+                        pos: pos3,
+                    },
                 )),
-                (Term::Str(_), t2, _) => Err(EvalError::TypeError(
+                (Term::Str(_), _, _) => Err(EvalError::TypeError(
                     String::from("Str"),
                     String::from("strReplace, 2nd argument"),
                     snd_pos,
-                    RichTerm::new(t2, pos2),
+                    RichTerm {
+                        term: snd,
+                        pos: pos2,
+                    },
                 )),
-                (t1, _, _) => Err(EvalError::TypeError(
+                (_, _, _) => Err(EvalError::TypeError(
                     String::from("Str"),
                     String::from("strReplace, 1st argument"),
                     fst_pos,
-                    RichTerm::new(t1, pos1),
+                    RichTerm {
+                        term: fst,
+                        pos: pos1,
+                    },
                 )),
             }
         }
@@ -2182,23 +2254,26 @@ fn process_nary_operation(
             ) = args_iter.next().unwrap();
             debug_assert!(args_iter.next().is_none());
 
-            if let Term::Lbl(lbl) = *t1 {
-                merge(
-                    RichTerm {
-                        term: t2,
-                        pos: pos2,
-                    },
-                    env2,
-                    RichTerm {
-                        term: t3,
-                        pos: pos3,
-                    },
-                    env3,
-                    pos_op,
-                    MergeMode::Contract(lbl),
-                )
-            } else {
-                Err(EvalError::InternalError(format!("The MergeContract() operator was expecting a first argument of type Label, got {}", t1.type_of().unwrap_or_else(|| String::from("<unevaluated>"))), pos_op))
+            match_sharedterm! {t1, with {
+                    Term::Lbl(lbl) => {
+                        merge(
+                            RichTerm {
+                                term: t2,
+                                pos: pos2,
+                            },
+                            env2,
+                            RichTerm {
+                                term: t3,
+                                pos: pos3,
+                            },
+                            env3,
+                            pos_op,
+                            MergeMode::Contract(lbl),
+                        )
+                    }
+                } else {
+                    Err(EvalError::InternalError(format!("The MergeContract() operator was expecting a first argument of type Label, got {}", t1.type_of().unwrap_or(String::from("<unevaluated>"))), pos_op))
+                }
             }
         }
     }
@@ -2259,7 +2334,7 @@ fn eq(env: &mut Environment, c1: Closure, c2: Closure) -> EqResult {
         }
     }
 
-    match (*t1, *t2) {
+    match (t1.into_owned(), t2.into_owned()) {
         (Term::Null, Term::Null) => EqResult::Bool(true),
         (Term::Bool(b1), Term::Bool(b2)) => EqResult::Bool(b1 == b2),
         (Term::Num(n1), Term::Num(n2)) => EqResult::Bool(n1 == n2),
diff --git a/src/grammar.lalrpop b/src/grammar.lalrpop
index 62978e43..0e6ff6d8 100644
--- a/src/grammar.lalrpop
+++ b/src/grammar.lalrpop
@@ -14,7 +14,7 @@ use crate::identifier::Ident;
 use crate::destruct::{Match, LastMatch, Destruct};
 use crate::parser::error::ParseError;
 use crate::term::{BinaryOp, RichTerm, Term, UnaryOp, StrChunk, MetaValue,
-    MergePriority, Contract, NAryOp, RecordAttrs};
+    MergePriority, Contract, NAryOp, RecordAttrs, SharedTerm};
 use crate::term::make as mk_term;
 use crate::types::{Types, AbsType};
 
@@ -109,7 +109,7 @@ RootTerm: RichTerm = {
     <l: @L> "fun" <ids:Ident+> "=>" <t: Term> <r: @R> => {
         let pos = mk_pos(src_id, l, r);
         ids.into_iter().rev().fold(t, |t, p| RichTerm {
-            term: Box::new(Term::Fun(p, t)),
+            term: SharedTerm::new(Term::Fun(p, t)),
             pos,
         })
     },
@@ -138,7 +138,7 @@ RootTerm: RichTerm = {
     "if" <cond: Term> "then" <t1: Term> "else" <t2: Term> =>
         mk_app!(Term::Op1(UnaryOp::Ite(), cond), t1, t2),
     AnnotatedTerm,
-    <l: @L> <t: !> <r: @R> => { errors.push(t); RichTerm { term: Box::new(Term::ParseError), pos: mk_pos(src_id, l, r)} },
+    <l: @L> <t: !> <r: @R> => { errors.push(t); RichTerm { term: SharedTerm::new(Term::ParseError), pos: mk_pos(src_id, l, r)} },
 };
 
 AnnotatedTerm: RichTerm = {
diff --git a/src/identifier.rs b/src/identifier.rs
index 200e7346..570aab72 100644
--- a/src/identifier.rs
+++ b/src/identifier.rs
@@ -70,3 +70,9 @@ impl Ident {
         self.label.starts_with(GEN_PREFIX)
     }
 }
+
+impl AsRef<str> for Ident {
+    fn as_ref(&self) -> &str {
+        &self.label
+    }
+}
diff --git a/src/program.rs b/src/program.rs
index f111672a..5d6fe862 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -209,7 +209,7 @@ mod tests {
     use crate::error::EvalError;
     use crate::parser::{grammar, lexer};
     use crate::position::TermPos;
-    use crate::term::Term;
+    use crate::term::SharedTerm;
     use codespan::Files;
     use std::io::Cursor;
 
@@ -244,7 +244,7 @@ mod tests {
         let mut expd = parse("[2, \"ab\", [1, [3]]]").unwrap();
 
         // String are parsed as StrChunks, but evaluated to Str, so we need to hack list a bit
-        if let Term::List(ref mut data) = *expd.term {
+        if let Term::List(ref mut data) = SharedTerm::make_mut(&mut expd.term) {
             *data.get_mut(1).unwrap() = mk_term::string("ab");
         } else {
             panic!();
diff --git a/src/term.rs b/src/term.rs
index 31f54efb..ad8e728d 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -19,6 +19,7 @@
 use crate::destruct::Destruct;
 use crate::identifier::Ident;
 use crate::label::Label;
+use crate::match_sharedterm;
 use crate::position::TermPos;
 use crate::types::{AbsType, Types};
 use codespan::FileId;
@@ -26,6 +27,8 @@ use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use std::ffi::OsString;
 use std::fmt;
+use std::ops::Deref;
+use std::rc::Rc;
 
 /// The AST of a Nickel expression.
 ///
@@ -574,6 +577,47 @@ impl Term {
     }
 }
 
+#[derive(Debug, Clone, PartialEq)]
+pub struct SharedTerm {
+    shared: Rc<Term>,
+}
+
+impl SharedTerm {
+    pub fn new(term: Term) -> Self {
+        Self {
+            shared: Rc::new(term),
+        }
+    }
+
+    pub fn into_owned(self) -> Term {
+        Rc::try_unwrap(self.shared).unwrap_or_else(|rc| Term::clone(&rc))
+    }
+
+    pub fn make_mut(this: &mut Self) -> &mut Term {
+        Rc::make_mut(&mut this.shared)
+    }
+}
+
+impl AsRef<Term> for SharedTerm {
+    fn as_ref(&self) -> &Term {
+        self.shared.as_ref()
+    }
+}
+
+impl From<SharedTerm> for Term {
+    fn from(st: SharedTerm) -> Self {
+        st.into_owned()
+    }
+}
+
+impl Deref for SharedTerm {
+    type Target = Term;
+
+    fn deref(&self) -> &Self::Target {
+        self.as_ref()
+    }
+}
+
 /// Primitive unary operators.
 ///
 /// Some operators, such as if-then-else or `seq`, actually take several arguments but are only
@@ -866,7 +910,7 @@ pub enum TraverseMethod {
 /// Wrap [terms](type.Term.html) with positional information.
 #[derive(Debug, PartialEq, Clone)]
 pub struct RichTerm {
-    pub term: Box<Term>,
+    pub term: SharedTerm,
     pub pos: TermPos,
 }
 
@@ -874,7 +918,7 @@ impl RichTerm {
     /// Create a new value from a term and an optional position.
     pub fn new(t: Term, pos: TermPos) -> Self {
         RichTerm {
-            term: Box::new(t),
+            term: SharedTerm::new(t),
             pos,
         }
     }
@@ -886,7 +930,7 @@ impl RichTerm {
     pub fn without_pos(mut self) -> Self {
         fn clean_pos(rt: &mut RichTerm) {
             rt.pos = TermPos::None;
-            rt.term
+            SharedTerm::make_mut(&mut rt.term)
                 .apply_to_rich_terms(|rt: &mut RichTerm| clean_pos(rt));
         }
 
@@ -912,57 +956,44 @@ impl RichTerm {
     where
         F: FnMut(RichTerm, &mut S) -> Result<RichTerm, E>,
     {
-        let RichTerm { term, pos } = match method {
+        let rt = match method {
             TraverseMethod::TopDown => f(self, state)?,
             TraverseMethod::BottomUp => self,
         };
+        let pos = rt.pos;
 
-        let result = match *term {
-            v @ Term::ParseError
-            | v @ Term::Null
-            | v @ Term::Bool(_)
-            | v @ Term::Num(_)
-            | v @ Term::Str(_)
-            | v @ Term::Lbl(_)
-            | v @ Term::Sym(_)
-            | v @ Term::Var(_)
-            | v @ Term::Enum(_)
-            | v @ Term::Import(_)
-            | v @ Term::ResolvedImport(_) => RichTerm {
-                term: Box::new(v),
-                pos,
-            },
+        let result = match_sharedterm! {rt.term, with {
             Term::Fun(id, t) => {
                 let t = t.traverse(f, state, method)?;
-                RichTerm {
-                    term: Box::new(Term::Fun(id, t)),
+                RichTerm::new(
+                    Term::Fun(id, t),
                     pos,
-                }
-            }
+                )
+            },
             Term::Let(id, t1, t2, btype) => {
                 let t1 = t1.traverse(f, state, method)?;
                 let t2 = t2.traverse(f, state, method)?;
-                RichTerm {
-                    term: Box::new(Term::Let(id, t1, t2, btype)),
+                RichTerm::new(
+                    Term::Let(id, t1, t2, btype),
                     pos,
-                }
-            }
+                )
+            },
             Term::LetPattern(id, pat, t1, t2) => {
                 let t1 = t1.traverse(f, state, method)?;
                 let t2 = t2.traverse(f, state, method)?;
-                RichTerm {
-                    term: Box::new(Term::LetPattern(id, pat, t1, t2)),
+                RichTerm::new(
+                    Term::LetPattern(id, pat, t1, t2),
                     pos,
-                }
-            }
+                )
+            },
             Term::App(t1, t2) => {
                 let t1 = t1.traverse(f, state, method)?;
                 let t2 = t2.traverse(f, state, method)?;
-                RichTerm {
-                    term: Box::new(Term::App(t1, t2)),
+                RichTerm::new(
+                    Term::App(t1, t2),
                     pos,
-                }
-            }
+                )
+            },
             Term::Switch(t, cases, default) => {
                 // The annotation on `map_res` use Result's corresponding trait to convert from
                 // Iterator<Result> to a Result<Iterator>
@@ -976,40 +1007,42 @@ impl RichTerm {
 
                 let t = t.traverse(f, state, method)?;
 
-                RichTerm::new(Term::Switch(t, cases_res?, default), pos)
-            }
+                RichTerm::new(
+                    Term::Switch(t, cases_res?, default),
+                    pos,
+                )
+            },
             Term::Op1(op, t) => {
                 let t = t.traverse(f, state, method)?;
-                RichTerm {
-                    term: Box::new(Term::Op1(op, t)),
+                RichTerm::new(
+                    Term::Op1(op, t),
                     pos,
-                }
-            }
+                )
+            },
             Term::Op2(op, t1, t2) => {
                 let t1 = t1.traverse(f, state, method)?;
                 let t2 = t2.traverse(f, state, method)?;
-                RichTerm {
-                    term: Box::new(Term::Op2(op, t1, t2)),
+                RichTerm::new(Term::Op2(op, t1, t2),
                     pos,
-                }
-            }
+                )
+            },
             Term::OpN(op, ts) => {
                 let ts_res: Result<Vec<RichTerm>, E> = ts
                     .into_iter()
                     .map(|t| t.traverse(f, state, method))
                     .collect();
-                RichTerm {
-                    term: Box::new(Term::OpN(op, ts_res?)),
+                RichTerm::new(
+                    Term::OpN(op, ts_res?),
                     pos,
-                }
-            }
+                )
+            },
             Term::Wrapped(i, t) => {
                 let t = t.traverse(f, state, method)?;
-                RichTerm {
-                    term: Box::new(Term::Wrapped(i, t)),
+                RichTerm::new(
+                    Term::Wrapped(i, t),
                     pos,
-                }
-            }
+                )
+            },
             Term::Record(map, attrs) => {
                 // The annotation on `map_res` uses Result's corresponding trait to convert from
                 // Iterator<Result> to a Result<Iterator>
@@ -1018,11 +1051,11 @@ impl RichTerm {
                     // For the conversion to work, note that we need a Result<(Ident,RichTerm), E>
                     .map(|(id, t)| t.traverse(f, state, method).map(|t_ok| (id.clone(), t_ok)))
                     .collect();
-                RichTerm {
-                    term: Box::new(Term::Record(map_res?, attrs)),
+                RichTerm::new(
+                    Term::Record(map_res?, attrs),
                     pos,
-                }
-            }
+                )
+            },
             Term::RecRecord(map, dyn_fields, attrs) => {
                 // The annotation on `map_res` uses Result's corresponding trait to convert from
                 // Iterator<Result> to a Result<Iterator>
@@ -1040,22 +1073,22 @@ impl RichTerm {
                         ))
                     })
                     .collect();
-                RichTerm {
-                    term: Box::new(Term::RecRecord(map_res?, dyn_fields_res?, attrs)),
+                RichTerm::new(
+                    Term::RecRecord(map_res?, dyn_fields_res?, attrs),
                     pos,
-                }
-            }
+                )
+            },
             Term::List(ts) => {
                 let ts_res: Result<Vec<RichTerm>, E> = ts
                     .into_iter()
                     .map(|t| t.traverse(f, state, method))
                     .collect();
 
-                RichTerm {
-                    term: Box::new(Term::List(ts_res?)),
+                RichTerm::new(
+                    Term::List(ts_res?),
                     pos,
-                }
-            }
+                )
+            },
             Term::StrChunks(chunks) => {
                 let chunks_res: Result<Vec<StrChunk<RichTerm>>, E> = chunks
                     .into_iter()
@@ -1067,11 +1100,11 @@ impl RichTerm {
                     })
                     .collect();
 
-                RichTerm {
-                    term: Box::new(Term::StrChunks(chunks_res?)),
+                RichTerm::new(
+                    Term::StrChunks(chunks_res?),
                     pos,
-                }
-            }
+                )
+            },
             Term::MetaValue(meta) => {
                 let contracts: Result<Vec<Contract>, _> = meta
                     .contracts
@@ -1105,20 +1138,18 @@ impl RichTerm {
                     .value
                     .map(|t| t.traverse(f, state, method))
                     .map_or(Ok(None), |res| res.map(Some))?;
-
-                let meta = MetaValue {
-                    doc: meta.doc,
-                    types,
-                    contracts,
-                    priority: meta.priority,
-                    value,
-                };
-
-                RichTerm {
-                    term: Box::new(Term::MetaValue(meta)),
+                    let meta = MetaValue {
+                        doc: meta.doc,
+                        types,
+                        contracts,
+                        priority: meta.priority,
+                        value,
+                    };
+                RichTerm::new(
+                    Term::MetaValue(meta),
                     pos,
-                }
-            }
+                )
+            }} else rt
         };
 
         match method {
@@ -1130,7 +1161,7 @@ impl RichTerm {
 
 impl From<RichTerm> for Term {
     fn from(rt: RichTerm) -> Self {
-        *rt.term
+        rt.term.into_owned()
     }
 }
 
@@ -1143,12 +1174,61 @@ impl AsRef<Term> for RichTerm {
 impl From<Term> for RichTerm {
     fn from(t: Term) -> Self {
         RichTerm {
-            term: Box::new(t),
+            term: SharedTerm::new(t),
             pos: TermPos::None,
         }
     }
 }
 
+/// Allows to match on SharedTerm without taking ownership of the matched part until the match.
+/// In the `else` clause, we haven't taken ownership yet, so we can still use the richterm at that point.
+///
+/// It is used somehow as a match statement, going from
+/// ```
+/// # use nickel::term::{RichTerm, Term};
+/// let rt = RichTerm::from(Term::Num(5.0));
+///
+/// match rt.term.into_owned() {
+///     Term::Num(x) => x as usize,
+///     Term::Str(s) => s.len(),
+///     _ => 42,
+/// };
+/// ```
+/// to
+/// ```
+/// # use nickel::term::{RichTerm, Term};
+/// # use nickel::match_sharedterm;
+/// let rt = RichTerm::from(Term::Num(5.0));
+///
+/// match_sharedterm!{rt.term, with {
+///         Term::Num(x) => x as usize,
+///         Term::Str(s) => s.len(),
+///     } else 42
+/// };
+/// ```
+///
+/// Known limitation: cannot use a `mut` inside the patterns.
+#[macro_export]
+macro_rules! match_sharedterm {
+    (
+        $st: expr, with {
+            $($($pat: pat_param)|+ $(if $if_expr: expr)? => $expr: expr),+ $(,)?
+        } else $else_clause: expr
+    ) => {
+        match $st.as_ref() {
+            $(
+                #[allow(unused_variables, unreachable_patterns, unused_mut)]
+                $($pat)|+ $(if $if_expr)? =>
+                    match Term::from($st) {
+                        $($pat)|+ => $expr,
+                        _ => unsafe {::core::hint::unreachable_unchecked()}
+                    },
+            )+
+            _ => $else_clause
+        }
+    };
+}
+
 #[macro_use]
 /// Helpers to build `RichTerm` objects.
 pub mod make {
diff --git a/src/transform/apply_contracts.rs b/src/transform/apply_contracts.rs
index dae387fd..a74664d2 100644
--- a/src/transform/apply_contracts.rs
+++ b/src/transform/apply_contracts.rs
@@ -8,31 +8,32 @@
 //!
 //! It must be run before `share_normal_form` to avoid rechecking contracts each time the inner
 //! value is unwrapped.
-use crate::mk_app;
 use crate::term::{RichTerm, Term};
+use crate::{match_sharedterm, mk_app};
 
 /// If the top-level node of the AST is a meta-value, apply the meta-value's contracts to the inner
 /// value.  Otherwise, return the term unchanged.
 pub fn transform_one(rt: RichTerm) -> RichTerm {
-    let RichTerm { term, pos } = rt;
+    let pos = rt.pos;
+    match_sharedterm! {rt.term,
+        with {
+            Term::MetaValue(meta) if meta.value.is_some() => {
+                let mut meta = meta;
+                let inner = meta.types.iter().chain(meta.contracts.iter()).fold(
+                    meta.value.take().unwrap(),
+                    |acc, ctr| {
+                        mk_app!(
+                            ctr.types.clone().contract(),
+                            Term::Lbl(ctr.label.clone()),
+                            acc
+                        )
+                        .with_pos(pos)
+                    },
+                );
 
-    match *term {
-        Term::MetaValue(mut meta) if meta.value.is_some() => {
-            let inner = meta.types.iter().chain(meta.contracts.iter()).fold(
-                meta.value.take().unwrap(),
-                |acc, ctr| {
-                    mk_app!(
-                        ctr.types.clone().contract(),
-                        Term::Lbl(ctr.label.clone()),
-                        acc
-                    )
-                    .with_pos(pos.into_inherited())
-                },
-            );
-
-            meta.value.replace(inner);
-            RichTerm::new(Term::MetaValue(meta), pos.into_inherited())
-        }
-        t => RichTerm::new(t, pos),
+                meta.value.replace(inner);
+                RichTerm::new(Term::MetaValue(meta), pos)
+            }
+        } else rt
     }
 }
diff --git a/src/transform/desugar_destructuring.rs b/src/transform/desugar_destructuring.rs
index 191ba702..a1931424 100644
--- a/src/transform/desugar_destructuring.rs
+++ b/src/transform/desugar_destructuring.rs
@@ -19,6 +19,7 @@
 //! ```
 use crate::destruct::{Destruct, Match};
 use crate::identifier::Ident;
+use crate::match_sharedterm;
 use crate::term::make::{op1, op2};
 use crate::term::{
     BinaryOp::DynRemove, BindingType, MetaValue, RichTerm, Term, UnaryOp::StaticAccess,
@@ -29,44 +30,48 @@ use crate::term::{
 /// record. This function should be, in the general case, considered as the entry point of this
 /// transformation.
 pub fn desugar_with_contract(rt: RichTerm) -> RichTerm {
-    if let Term::LetPattern(x, pat, t_, body) = *rt.term {
-        let pos = body.pos;
-        let meta = pat.clone().as_contract();
-        let t_ = {
-            let t_pos = t_.pos;
-            RichTerm::new(
-                Term::MetaValue(MetaValue {
-                    value: Some(t_),
-                    ..meta
-                }),
-                t_pos,
-            )
-        };
-        desugar(RichTerm::new(Term::LetPattern(x, pat, t_, body), pos))
-    } else {
-        rt
-    }
+    match_sharedterm!(rt.term,
+        with {
+            Term::LetPattern(x, pat, t_, body) => {
+                let pos = body.pos;
+                let meta = pat.clone().as_contract();
+                let t_ = {
+                    let t_pos = t_.pos;
+                    RichTerm::new(
+                        Term::MetaValue(MetaValue {
+                            value: Some(t_),
+                            ..meta
+                        }),
+                        t_pos,
+                    )
+                };
+                desugar(RichTerm::new(Term::LetPattern(x, pat, t_, body), pos))
+            }
+        } else rt
+    )
 }
 
 /// Main transformation function to desugar let patterns. WARNING: In a real usage case, you will
 /// want to generate also the metavalue associated to this pattern destructuring. Do not consider
 /// this function as the entry point of the transformation. For that, use `desugar_with_contract`.
 pub fn desugar(rt: RichTerm) -> RichTerm {
-    if let Term::LetPattern(x, pat, t_, body) = *rt.term {
-        let pos = body.pos;
-        let x = x.unwrap_or_else(super::fresh_var);
-        RichTerm::new(
-            Term::Let(
-                x.clone(),
-                t_,
-                destruct_term(x.clone(), &pat, bind_open_field(x, &pat, body)),
-                BindingType::Normal,
-            ),
-            pos,
-        )
-    } else {
-        rt
-    }
+    match_sharedterm!(rt.term,
+        with {
+            Term::LetPattern(x, pat, t_, body) => {
+                let pos = body.pos;
+                let x = x.unwrap_or_else(super::fresh_var);
+                RichTerm::new(
+                    Term::Let(
+                        x.clone(),
+                        t_,
+                        destruct_term(x.clone(), &pat, bind_open_field(x, &pat, body)),
+                        BindingType::Normal,
+                    ),
+                    pos,
+                )
+            }
+        } else rt
+    )
 }
 
 /// Wrap `body` in a let construct binding the open part of the pattern to the required value.
diff --git a/src/transform/share_normal_form.rs b/src/transform/share_normal_form.rs
index e4ccf79b..79809d3e 100644
--- a/src/transform/share_normal_form.rs
+++ b/src/transform/share_normal_form.rs
@@ -29,8 +29,9 @@
 //! variables.
 use super::fresh_var;
 use crate::identifier::Ident;
+use crate::match_sharedterm;
 use crate::position::TermPos;
-use crate::term::{BindingType, MetaValue, RichTerm, Term};
+use crate::term::{BindingType, RichTerm, Term};
 
 /// Transform the top-level term of an AST to a share normal form, if it can.
 ///
@@ -41,115 +42,106 @@ use crate::term::{BindingType, MetaValue, RichTerm, Term};
 /// the transformation is implemented as rewrite rules, and must be used in conjunction a
 /// traversal to obtain a full transformation.
 pub fn transform_one(rt: RichTerm) -> RichTerm {
-    let RichTerm { term, pos } = rt;
-    match *term {
-        Term::Record(map, attrs) => {
-            let mut bindings = Vec::with_capacity(map.len());
+    let pos = rt.pos;
+    match_sharedterm! {rt.term,
+        with {
+            Term::Record(map, attrs) => {
+                let mut bindings = Vec::with_capacity(map.len());
 
-            let map = map
-                .into_iter()
-                .map(|(id, t)| {
-                    if should_share(&t.term) {
-                        let fresh_var = fresh_var();
-                        let pos_t = t.pos;
-                        bindings.push((fresh_var.clone(), t));
-                        (id, RichTerm::new(Term::Var(fresh_var), pos_t))
-                    } else {
-                        (id, t)
-                    }
-                })
-                .collect();
+                let map = map
+                    .into_iter()
+                    .map(|(id, t)| {
+                        if should_share(&t.term) {
+                            let fresh_var = fresh_var();
+                            let pos_t = t.pos;
+                            bindings.push((fresh_var.clone(), t));
+                            (id, RichTerm::new(Term::Var(fresh_var), pos_t))
+                        } else {
+                            (id, t)
+                        }
+                    })
+                    .collect();
 
-            with_bindings(Term::Record(map, attrs), bindings, pos, BindingType::Normal)
-        }
-        Term::RecRecord(map, dyn_fields, attrs) => {
-            // When a recursive record is evaluated, all fields need to be turned to closures
-            // anyway (see the corresponding case in `eval::eval()`), which is what the share
-            // normal form transformation does. This is why the test is more lax here than for
-            // other constructors: it is not only about sharing, but also about the future
-            // evaluation of recursive records. Only constant are not required to be
-            // closurized.
-            //
-            // In theory, the variable case is one exception: if the field is already a bare
-            // variable, it seems useless to add one more indirection through a generated
-            // variable. However, it is currently fundamental for recursive record merging that
-            // the sare normal form transformation ensure the following post-condition: the
-            // fields of recursive records contain either a constant or a *generated* variable,
-            // but never a user-supplied variable directly (the former starts with a special
-            // marker). See comments inside [`RichTerm::closurize`] for more details.
-            let mut bindings = Vec::with_capacity(map.len());
+                with_bindings(Term::Record(map, attrs), bindings, pos, BindingType::Normal)
+            },
+            Term::RecRecord(map, dyn_fields, attrs) => {
+                // When a recursive record is evaluated, all fields need to be turned to closures
+                // anyway (see the corresponding case in `eval::eval()`), which is what the share
+                // normal form transformation does. This is why the test is more lax here than for
+                // other constructors: it is not only about sharing, but also about the future
+                // evaluation of recursive records. Only constant are not required to be
+                // closurized.
+                //
+                // In theory, the variable case is one exception: if the field is already a bare
+                // variable, it seems useless to add one more indirection through a generated
+                // variable. However, it is currently fundamental for recursive record merging that
+                // the sare normal form transformation ensure the following post-condition: the
+                // fields of recursive records contain either a constant or a *generated* variable,
+                // but never a user-supplied variable directly (the former starts with a special
+                // marker). See comments inside [`RichTerm::closurize`] for more details.
+                let mut bindings = Vec::with_capacity(map.len());
 
-            let map = map
-                .into_iter()
-                .map(|(id, t)| {
-                    // CHANGE THIS CONDITION CAREFULLY. Doing so can break the post-condition
-                    // explained above.
-                    if !t.as_ref().is_constant() {
-                        let fresh_var = fresh_var();
-                        let pos_t = t.pos;
-                        bindings.push((fresh_var.clone(), t));
-                        (id, RichTerm::new(Term::Var(fresh_var), pos_t))
-                    } else {
-                        (id, t)
-                    }
-                })
-                .collect();
+                let map = map
+                    .into_iter()
+                    .map(|(id, t)| {
+                        // CHANGE THIS CONDITION CAREFULLY. Doing so can break the post-condition
+                        // explained above.
+                        if !t.as_ref().is_constant() {
+                            let fresh_var = fresh_var();
+                            let pos_t = t.pos;
+                            bindings.push((fresh_var.clone(), t));
+                            (id, RichTerm::new(Term::Var(fresh_var), pos_t))
+                        } else {
+                            (id, t)
+                        }
+                    })
+                    .collect();
 
-            let dyn_fields = dyn_fields
-                .into_iter()
-                .map(|(id_t, t)| {
-                    if !t.as_ref().is_constant() {
-                        let fresh_var = fresh_var();
-                        let pos_t = t.pos;
-                        bindings.push((fresh_var.clone(), t));
-                        (id_t, RichTerm::new(Term::Var(fresh_var), pos_t))
-                    } else {
-                        (id_t, t)
-                    }
-                })
-                .collect();
+                let dyn_fields = dyn_fields
+                    .into_iter()
+                    .map(|(id_t, t)| {
+                        if !t.as_ref().is_constant() {
+                            let fresh_var = fresh_var();
+                            let pos_t = t.pos;
+                            bindings.push((fresh_var.clone(), t));
+                            (id_t, RichTerm::new(Term::Var(fresh_var), pos_t))
+                        } else {
+                            (id_t, t)
+                        }
+                    })
+                    .collect();
 
-            // Recursive records are the reason why we need revertible thunks, since when
-            // merged, we may have to revert the fields back to their original expression.
-            with_bindings(
-                Term::RecRecord(map, dyn_fields, attrs),
-                bindings,
-                pos,
-                BindingType::Revertible,
-            )
-        }
-        Term::List(ts) => {
-            let mut bindings = Vec::with_capacity(ts.len());
+                with_bindings(Term::RecRecord(map, dyn_fields, attrs), bindings, pos, BindingType::Revertible)
+            },
+            Term::List(ts) => {
+                let mut bindings = Vec::with_capacity(ts.len());
 
-            let ts = ts
-                .into_iter()
-                .map(|t| {
-                    if should_share(&t.term) {
-                        let fresh_var = fresh_var();
-                        let pos_t = t.pos;
-                        bindings.push((fresh_var.clone(), t));
-                        RichTerm::new(Term::Var(fresh_var), pos_t)
-                    } else {
-                        t
-                    }
-                })
-                .collect();
+                let ts = ts
+                    .into_iter()
+                    .map(|t| {
+                        if should_share(&t.term) {
+                            let fresh_var = fresh_var();
+                            let pos_t = t.pos;
+                            bindings.push((fresh_var.clone(), t));
+                            RichTerm::new(Term::Var(fresh_var), pos_t)
+                        } else {
+                            t
+                        }
+                    })
+                    .collect();
 
-            with_bindings(Term::List(ts), bindings, pos, BindingType::Normal)
-        }
-        Term::MetaValue(mut meta @ MetaValue { value: Some(_), .. }) => {
-            if meta.value.as_ref().map(|t| should_share(&t.term)).unwrap() {
-                let fresh_var = fresh_var();
-                let t = meta.value.take().unwrap();
-                meta.value
-                    .replace(RichTerm::new(Term::Var(fresh_var.clone()), t.pos));
-                let inner = RichTerm::new(Term::MetaValue(meta), pos);
-                RichTerm::new(Term::Let(fresh_var, t, inner, BindingType::Normal), pos)
-            } else {
-                RichTerm::new(Term::MetaValue(meta), pos)
+                with_bindings(Term::List(ts), bindings, pos, BindingType::Normal)
+            },
+            Term::MetaValue(meta) if meta.value.as_ref().map(|t| should_share(&t.term)).unwrap_or(false) => {
+                    let mut meta = meta;
+                    let fresh_var = fresh_var();
+                    let t = meta.value.take().unwrap();
+                    meta.value
+                        .replace(RichTerm::new(Term::Var(fresh_var.clone()), t.pos));
+                    let inner = RichTerm::new(Term::MetaValue(meta), pos);
+                    RichTerm::new(Term::Let(fresh_var, t, inner, BindingType::Normal), pos)
             }
-        }
-        t => RichTerm::new(t, pos),
+        } else rt
     }
 }
 
diff --git a/src/types.rs b/src/types.rs
index 8ac92f59..c8d1c67c 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -433,7 +433,7 @@ mod test {
             .parse_term(id, Lexer::new(&wrapper))
             .unwrap();
 
-        match *rt.term {
+        match rt.term.into_owned() {
             Term::MetaValue(MetaValue { mut contracts, .. }) if contracts.len() == 1 => {
                 contracts.remove(0).types
             }


Test patch:
diff --git a/tests/query.rs b/tests/query.rs
index 370d9e79..fbdec93e 100644
--- a/tests/query.rs
+++ b/tests/query.rs
@@ -1,5 +1,5 @@
 use nickel::program::Program;
-use nickel::term::Term;
+use nickel::term::{SharedTerm, Term};
 
 #[test]
 pub fn test_query_metadata_basic() {
@@ -9,7 +9,7 @@ pub fn test_query_metadata_basic() {
 
     if let Term::MetaValue(meta) = result {
         assert_eq!(meta.doc, Some(String::from("Test basic")));
-        assert_eq!(meta.value.unwrap().term, Box::new(Term::Num(2.0)));
+        assert_eq!(meta.value.unwrap().term, SharedTerm::new(Term::Num(2.0)));
     } else {
         panic!();
     }
@@ -26,7 +26,7 @@ pub fn test_query_metadata_from_func() {
 
     if let Term::MetaValue(meta) = result {
         assert_eq!(meta.doc, Some(String::from("Test from func")));
-        assert_eq!(meta.value.unwrap().term, Box::new(Term::Num(4.0)));
+        assert_eq!(meta.value.unwrap().term, SharedTerm::new(Term::Num(4.0)));
     } else {
         panic!();
     }