diff --git a/src/binary/history.rs b/src/binary/history.rs
index 8681b0a41dbb867d305eab28bc023f4503c3bf21..e5a432f30d7c4b44574e54991dc8fd748c4088f3 100644
--- a/src/binary/history.rs
+++ b/src/binary/history.rs
@@ -1,5 +1,5 @@
 use super::InteractiveBinary;
-use ion_shell::{status::*, types};
+use ion_shell::{status::*, Value};
 
 use regex::Regex;
 use std::time::{SystemTime, UNIX_EPOCH};
@@ -26,40 +26,49 @@ impl<'a> InteractiveBinary<'a> {
     /// Updates the history ignore patterns. Call this whenever HISTORY_IGNORE
     /// is changed.
     pub fn ignore_patterns(&self) -> IgnoreSetting {
-        let patterns: types::Array = self.shell.borrow().variables().get("HISTORY_IGNORE").unwrap();
-        let mut settings = IgnoreSetting::default();
-        let mut regexes = Vec::new();
-        // for convenience and to avoid typos
-        let regex_prefix = "regex:";
-        for pattern in patterns.into_iter() {
-            let pattern = format!("{}", pattern);
-            match pattern.as_ref() {
-                "all" => settings.all = true,
-                "no_such_command" => settings.no_such_command = true,
-                "whitespace" => settings.whitespace = true,
-                "duplicates" => settings.duplicates = true,
-                // The length check is there to just ignore empty regex definitions
-                _ if pattern.starts_with(regex_prefix) && pattern.len() > regex_prefix.len() => {
-                    settings.based_on_regex = true;
-                    let regex_string = &pattern[regex_prefix.len()..];
-                    // We save the compiled regexes, as compiling them can be  an expensive task
-                    if let Ok(regex) = Regex::new(regex_string) {
-                        regexes.push(regex);
+        if let Some(Value::Array(patterns)) =
+            self.shell.borrow().variables().get_ref("HISTORY_IGNORE")
+        {
+            let mut settings = IgnoreSetting::default();
+            let mut regexes = Vec::new();
+            // for convenience and to avoid typos
+            let regex_prefix = "regex:";
+            for pattern in patterns.into_iter() {
+                let pattern = format!("{}", pattern);
+                match pattern.as_ref() {
+                    "all" => settings.all = true,
+                    "no_such_command" => settings.no_such_command = true,
+                    "whitespace" => settings.whitespace = true,
+                    "duplicates" => settings.duplicates = true,
+                    // The length check is there to just ignore empty regex definitions
+                    _ if pattern.starts_with(regex_prefix)
+                        && pattern.len() > regex_prefix.len() =>
+                    {
+                        settings.based_on_regex = true;
+                        let regex_string = &pattern[regex_prefix.len()..];
+                        // We save the compiled regexes, as compiling them can be  an expensive task
+                        if let Ok(regex) = Regex::new(regex_string) {
+                            regexes.push(regex);
+                        }
                     }
+                    _ => continue,
                 }
-                _ => continue,
             }
-        }
-        settings.regexes = if !regexes.is_empty() { Some(regexes) } else { None };
+            settings.regexes = if !regexes.is_empty() { Some(regexes) } else { None };
 
-        settings
+            settings
+        } else {
+            panic!("HISTORY_IGNORE is not set!");
+        }
     }
 
     /// Saves a command in the history, depending on @HISTORY_IGNORE. Should be called
     /// immediately after `on_command()`
     pub fn save_command_in_history(&self, command: &str) {
         if self.should_save_command(command) {
-            if self.shell.borrow().variables().get_str_or_empty("HISTORY_TIMESTAMP") == "1" {
+            if self.shell.borrow().variables().get_str("HISTORY_TIMESTAMP").unwrap_or_default()
+                == "1"
+            {
                 // Get current time stamp
                 let since_unix_epoch =
                     SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
diff --git a/src/binary/mod.rs b/src/binary/mod.rs
index 0203e9ec3e101ca9b49547eb9e16b893fde71f88..26578da726cf732ec0810308d6b40cd6b51e374e 100644
--- a/src/binary/mod.rs
+++ b/src/binary/mod.rs
@@ -10,7 +10,7 @@ use ion_shell::{
     builtins::man_pages,
     parser::{Expander, Terminator},
     status::{FAILURE, SUCCESS},
-    types, Shell,
+    Shell,
 };
 use itertools::Itertools;
 use liner::{Buffer, Context, KeyBindings};
@@ -57,8 +57,8 @@ impl<'a> InteractiveBinary<'a> {
     pub fn new(shell: Shell<'a>) -> Self {
         let mut context = Context::new();
         context.word_divider_fn = Box::new(word_divide);
-        if shell.variables().get_str_or_empty("HISTFILE_ENABLED") == "1" {
-            let path = shell.get::<types::Str>("HISTFILE").expect("shell didn't set HISTFILE");
+        if shell.variables().get_str("HISTFILE_ENABLED") == Some("1".into()) {
+            let path = shell.variables().get_str("HISTFILE").expect("shell didn't set HISTFILE");
             if !Path::new(path.as_str()).exists() {
                 eprintln!("ion: creating history file at \"{}\"", path);
             }
@@ -96,7 +96,7 @@ impl<'a> InteractiveBinary<'a> {
             // If `RECORD_SUMMARY` is set to "1" (True, Yes), then write a summary of the
             // pipline just executed to the the file and context histories. At the
             // moment, this means record how long it took.
-            if "1" == shell.variables().get_str_or_empty("RECORD_SUMMARY") {
+            if Some("1".into()) == shell.variables().get_str("RECORD_SUMMARY") {
                 let summary = format!(
                     "#summary# elapsed real time: {}.{:09} seconds",
                     elapsed.as_secs(),
diff --git a/src/binary/prompt.rs b/src/binary/prompt.rs
index 0212a566653ae6898e665a0f0562d2f317d1c416..df03a2573ece49c6b5a6ec1f9f8079e034cd2754 100644
--- a/src/binary/prompt.rs
+++ b/src/binary/prompt.rs
@@ -6,7 +6,10 @@ pub fn prompt(shell: &Shell) -> String {
 
     if blocks == 0 {
         prompt_fn(&shell).unwrap_or_else(|| {
-            shell.get_string(&shell.variables().get_str_or_empty("PROMPT")).as_str().into()
+            shell
+                .get_string(&shell.variables().get_str("PROMPT").unwrap_or_default())
+                .as_str()
+                .into()
         })
     } else {
         "    ".repeat(blocks)
diff --git a/src/lib/builtins/command_info.rs b/src/lib/builtins/command_info.rs
index 3a23d5375fedfd71fc394a4e9dc1f359b616a6cb..45d3ce276f29b8aa6c95d175d65c9fe60dc85730 100644
--- a/src/lib/builtins/command_info.rs
+++ b/src/lib/builtins/command_info.rs
@@ -1,7 +1,7 @@
 use crate::{
     builtins::man_pages::*,
-    shell::{flow_control::Function, status::*, Shell},
-    sys, types,
+    shell::{status::*, Shell, Value},
+    sys,
 };
 use small;
 
@@ -22,8 +22,8 @@ pub fn which(args: &[small::String], shell: &mut Shell) -> Result<i32, ()> {
         match get_command_info(command, shell) {
             Ok(c_type) => match c_type.as_ref() {
                 "alias" => {
-                    if let Some(alias) = shell.variables().get::<types::Alias>(&**command) {
-                        println!("{}: alias to {}", command, &*alias);
+                    if let Some(Value::Alias(ref alias)) = shell.variables().get_ref(&**command) {
+                        println!("{}: alias to {}", command, &**alias);
                     }
                 }
                 "function" => println!("{}: function", command),
@@ -49,8 +49,8 @@ pub fn find_type(args: &[small::String], shell: &mut Shell) -> Result<i32, ()> {
             Ok(c_type) => {
                 match c_type.as_ref() {
                     "alias" => {
-                        if let Some(alias) = shell.variables().get::<types::Alias>(&**command) {
-                            println!("{} is aliased to `{}`", command, &*alias);
+                        if let Some(Value::Alias(alias)) = shell.variables().get_ref(&**command) {
+                            println!("{} is aliased to `{}`", command, &**alias);
                         }
                     }
                     // TODO Make it print the function.
@@ -67,21 +67,20 @@ pub fn find_type(args: &[small::String], shell: &mut Shell) -> Result<i32, ()> {
 }
 
 pub fn get_command_info<'a>(command: &str, shell: &mut Shell) -> Result<Cow<'a, str>, ()> {
-    if shell.variables().get::<types::Alias>(command).is_some() {
-        return Ok("alias".into());
-    } else if shell.variables().get::<Function>(command).is_some() {
-        return Ok("function".into());
-    } else if shell.builtins().contains(command) {
-        return Ok("builtin".into());
-    } else {
-        for path in
-            env::var("PATH").unwrap_or_else(|_| String::from("/bin")).split(sys::PATH_SEPARATOR)
-        {
-            let executable = Path::new(path).join(command);
-            if executable.is_file() {
-                return Ok(executable.display().to_string().into());
+    match shell.variables().get_ref(command) {
+        Some(Value::Alias(_)) => Ok("alias".into()),
+        Some(Value::Function(_)) => Ok("function".into()),
+        _ if shell.builtins().contains(command) => Ok("builtin".into()),
+        _ => {
+            for path in
+                env::var("PATH").unwrap_or_else(|_| String::from("/bin")).split(sys::PATH_SEPARATOR)
+            {
+                let executable = Path::new(path).join(command);
+                if executable.is_file() {
+                    return Ok(executable.display().to_string().into());
+                }
             }
+            Err(())
         }
     }
-    Err(())
 }
diff --git a/src/lib/builtins/exists.rs b/src/lib/builtins/exists.rs
index 6283d7871a7fc94b8dd8332dbdcdc1ce4f716db2..265cda274b2b7920c4317256f2637b2a343b6875 100644
--- a/src/lib/builtins/exists.rs
+++ b/src/lib/builtins/exists.rs
@@ -1,11 +1,6 @@
 use std::{fs, os::unix::fs::PermissionsExt};
 
-#[cfg(test)]
-use crate::shell::{self, flow_control::Statement};
-use crate::{
-    shell::{flow_control::Function, Shell},
-    types,
-};
+use crate::shell::{Shell, Value};
 use small;
 
 pub fn exists(args: &[small::String], shell: &Shell) -> Result<bool, small::String> {
@@ -76,7 +71,7 @@ fn binary_is_in_path(binaryname: &str, shell: &Shell) -> bool {
     // TODO: Right now they use an entirely different logic which means that it
     // *might* be possible TODO: that `exists` reports a binary to be in the
     // path, while the shell cannot find it or TODO: vice-versa
-    if let Some(path) = shell.get::<types::Str>("PATH") {
+    if let Some(path) = shell.variables().get_str("PATH") {
         for fname in path.split(':').map(|dir| format!("{}/{}", dir, binaryname)) {
             if let Ok(metadata) = fs::metadata(&fname) {
                 if metadata.is_file() && file_has_execute_permission(&fname) {
@@ -113,15 +108,15 @@ fn string_is_nonzero(string: &str) -> bool { !string.is_empty() }
 
 /// Returns true if the variable is an array and the array is not empty
 fn array_var_is_not_empty(arrayvar: &str, shell: &Shell) -> bool {
-    match shell.variables().get::<types::Array>(arrayvar) {
-        Some(array) => !array.is_empty(),
-        None => false,
+    match shell.variables().get_ref(arrayvar) {
+        Some(Value::Array(array)) => !array.is_empty(),
+        _ => false,
     }
 }
 
 /// Returns true if the variable is a string and the string is not empty
 fn string_var_is_not_empty(stringvar: &str, shell: &Shell) -> bool {
-    match shell.variables().get::<types::Str>(stringvar) {
+    match shell.variables().get_str(stringvar) {
         Some(string) => !string.is_empty(),
         None => false,
     }
@@ -129,246 +124,278 @@ fn string_var_is_not_empty(stringvar: &str, shell: &Shell) -> bool {
 
 /// Returns true if a function with the given name is defined
 fn function_is_defined(function: &str, shell: &Shell) -> bool {
-    shell.variables().get::<Function>(function).is_some()
-}
-
-#[test]
-fn test_evaluate_arguments() {
-    use crate::lexers::assignments::{KeyBuf, Primitive};
-    let mut shell = shell::Shell::library();
-
-    // assert_eq!(exists(&["ion".into(), ], &mut sink, &shell), Ok(false));
-    // no parameters
-    assert_eq!(exists(&["ion".into()], &shell), Ok(false));
-    // multiple arguments
-    // ignores all but the first argument
-    assert_eq!(exists(&["ion".into(), "foo".into(), "bar".into()], &shell), Ok(true));
-
-    // check `exists STRING`
-    assert_eq!(exists(&["ion".into(), "".into()], &shell), Ok(false));
-    assert_eq!(exists(&["ion".into(), "string".into()], &shell), Ok(true));
-    assert_eq!(exists(&["ion".into(), "string with space".into()], &shell), Ok(true));
-    assert_eq!(exists(&["ion".into(), "-startswithdash".into()], &shell), Ok(true));
-
-    // check `exists -a`
-    // no argument means we treat it as a string
-    assert_eq!(exists(&["ion".into(), "-a".into()], &shell), Ok(true));
-    shell.variables_mut().set("emptyarray", types::Array::new());
-    assert_eq!(exists(&["ion".into(), "-a".into(), "emptyarray".into()], &shell), Ok(false));
-    let mut array = types::Array::new();
-    array.push("element".into());
-    shell.variables_mut().set("array", array);
-    assert_eq!(exists(&["ion".into(), "-a".into(), "array".into()], &shell), Ok(true));
-    shell.variables_mut().remove_variable("array");
-    assert_eq!(exists(&["ion".into(), "-a".into(), "array".into()], &shell), Ok(false));
-
-    // check `exists -b`
-    // TODO: see test_binary_is_in_path()
-    // no argument means we treat it as a string
-    assert_eq!(exists(&["ion".into(), "-b".into()], &shell), Ok(true));
-    let oldpath = shell.get::<types::Str>("PATH").unwrap_or_else(|| "/usr/bin".into());
-    shell.set("PATH", "testing/");
-
-    assert_eq!(exists(&["ion".into(), "-b".into(), "executable_file".into()], &shell), Ok(true));
-    assert_eq!(exists(&["ion".into(), "-b".into(), "empty_file".into()], &shell), Ok(false));
-    assert_eq!(
-        exists(&["ion".into(), "-b".into(), "file_does_not_exist".into()], &shell),
-        Ok(false)
-    );
-
-    // restore original PATH. Not necessary for the currently defined test cases
-    // but this might change in the future? Better safe than sorry!
-    shell.set("PATH", oldpath);
-
-    // check `exists -d`
-    // no argument means we treat it as a string
-    assert_eq!(exists(&["ion".into(), "-d".into()], &shell), Ok(true));
-    assert_eq!(exists(&["ion".into(), "-d".into(), "testing/".into()], &shell), Ok(true));
-    assert_eq!(
-        exists(&["ion".into(), "-d".into(), "testing/empty_file".into()], &shell),
-        Ok(false)
-    );
-    assert_eq!(exists(&["ion".into(), "-d".into(), "does/not/exist/".into()], &shell), Ok(false));
-
-    // check `exists -f`
-    // no argument means we treat it as a string
-    assert_eq!(exists(&["ion".into(), "-f".into()], &shell), Ok(true));
-    assert_eq!(exists(&["ion".into(), "-f".into(), "testing/".into()], &shell), Ok(false));
-    assert_eq!(exists(&["ion".into(), "-f".into(), "testing/empty_file".into()], &shell), Ok(true));
-    assert_eq!(exists(&["ion".into(), "-f".into(), "does-not-exist".into()], &shell), Ok(false));
-
-    // check `exists -s`
-    // no argument means we treat it as a string
-    assert_eq!(exists(&["ion".into(), "-s".into()], &shell), Ok(true));
-    shell.set("emptyvar", "".to_string());
-    assert_eq!(exists(&["ion".into(), "-s".into(), "emptyvar".into()], &shell), Ok(false));
-    shell.set("testvar", "foobar".to_string());
-    assert_eq!(exists(&["ion".into(), "-s".into(), "testvar".into()], &shell), Ok(true));
-    shell.variables_mut().remove_variable("testvar");
-    assert_eq!(exists(&["ion".into(), "-s".into(), "testvar".into()], &shell), Ok(false));
-    // also check that it doesn't trigger on arrays
-    let mut array = types::Array::new();
-    array.push("element".into());
-    shell.variables_mut().remove_variable("array");
-    shell.variables_mut().set("array", array);
-    assert_eq!(exists(&["ion".into(), "-s".into(), "array".into()], &shell), Ok(false));
-
-    // check `exists --fn`
-    let name_str = "test_function";
-    let name = small::String::from(name_str);
-    let mut args = Vec::new();
-    args.push(KeyBuf { name: "testy".into(), kind: Primitive::Str });
-    let mut statements = Vec::new();
-    statements.push(Statement::End);
-    let description: small::String = "description".into();
-
-    shell
-        .variables_mut()
-        .set(&name, Function::new(Some(description), name.clone(), args, statements));
-
-    assert_eq!(exists(&["ion".into(), "--fn".into(), name_str.into()], &shell), Ok(true));
-    shell.variables_mut().remove_variable(name_str);
-    assert_eq!(exists(&["ion".into(), "--fn".into(), name_str.into()], &shell), Ok(false));
-
-    // check invalid flags / parameters (should all be treated as strings and
-    // therefore succeed)
-    assert_eq!(exists(&["ion".into(), "--foo".into()], &shell), Ok(true));
-    assert_eq!(exists(&["ion".into(), "-x".into()], &shell), Ok(true));
+    if let Some(Value::Function(_)) = shell.variables().get_ref(function) {
+        true
+    } else {
+        false
+    }
 }
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{
+        flow_control::Function,
+        lexers::assignments::{KeyBuf, Primitive},
+        shell::flow_control::Statement,
+        types,
+    };
+    use small;
+
+    #[test]
+    fn test_evaluate_arguments() {
+        let mut shell = Shell::library();
+
+        // assert_eq!(exists(&["ion".into(), ], &mut sink, &shell), Ok(false));
+        // no parameters
+        assert_eq!(exists(&["ion".into()], &shell), Ok(false));
+        // multiple arguments
+        // ignores all but the first argument
+        assert_eq!(exists(&["ion".into(), "foo".into(), "bar".into()], &shell), Ok(true));
+
+        // check `exists STRING`
+        assert_eq!(exists(&["ion".into(), "".into()], &shell), Ok(false));
+        assert_eq!(exists(&["ion".into(), "string".into()], &shell), Ok(true));
+        assert_eq!(exists(&["ion".into(), "string with space".into()], &shell), Ok(true));
+        assert_eq!(exists(&["ion".into(), "-startswithdash".into()], &shell), Ok(true));
+
+        // check `exists -a`
+        // no argument means we treat it as a string
+        assert_eq!(exists(&["ion".into(), "-a".into()], &shell), Ok(true));
+        shell.variables_mut().set("emptyarray", types::Array::new());
+        assert_eq!(exists(&["ion".into(), "-a".into(), "emptyarray".into()], &shell), Ok(false));
+        let mut array = types::Array::new();
+        array.push("element".into());
+        shell.variables_mut().set("array", array);
+        assert_eq!(exists(&["ion".into(), "-a".into(), "array".into()], &shell), Ok(true));
+        shell.variables_mut().remove_variable("array");
+        assert_eq!(exists(&["ion".into(), "-a".into(), "array".into()], &shell), Ok(false));
+
+        // check `exists -b`
+        // TODO: see test_binary_is_in_path()
+        // no argument means we treat it as a string
+        assert_eq!(exists(&["ion".into(), "-b".into()], &shell), Ok(true));
+        let oldpath = shell.variables().get_str("PATH").unwrap_or_else(|| "/usr/bin".into());
+        shell.variables_mut().set("PATH", "testing/");
+
+        assert_eq!(
+            exists(&["ion".into(), "-b".into(), "executable_file".into()], &shell),
+            Ok(true)
+        );
+        assert_eq!(exists(&["ion".into(), "-b".into(), "empty_file".into()], &shell), Ok(false));
+        assert_eq!(
+            exists(&["ion".into(), "-b".into(), "file_does_not_exist".into()], &shell),
+            Ok(false)
+        );
+
+        // restore original PATH. Not necessary for the currently defined test cases
+        // but this might change in the future? Better safe than sorry!
+        shell.variables_mut().set("PATH", oldpath);
+
+        // check `exists -d`
+        // no argument means we treat it as a string
+        assert_eq!(exists(&["ion".into(), "-d".into()], &shell), Ok(true));
+        assert_eq!(exists(&["ion".into(), "-d".into(), "testing/".into()], &shell), Ok(true));
+        assert_eq!(
+            exists(&["ion".into(), "-d".into(), "testing/empty_file".into()], &shell),
+            Ok(false)
+        );
+        assert_eq!(
+            exists(&["ion".into(), "-d".into(), "does/not/exist/".into()], &shell),
+            Ok(false)
+        );
+
+        // check `exists -f`
+        // no argument means we treat it as a string
+        assert_eq!(exists(&["ion".into(), "-f".into()], &shell), Ok(true));
+        assert_eq!(exists(&["ion".into(), "-f".into(), "testing/".into()], &shell), Ok(false));
+        assert_eq!(
+            exists(&["ion".into(), "-f".into(), "testing/empty_file".into()], &shell),
+            Ok(true)
+        );
+        assert_eq!(
+            exists(&["ion".into(), "-f".into(), "does-not-exist".into()], &shell),
+            Ok(false)
+        );
+
+        // check `exists -s`
+        // no argument means we treat it as a string
+        assert_eq!(exists(&["ion".into(), "-s".into()], &shell), Ok(true));
+        shell.variables_mut().set("emptyvar", "".to_string());
+        assert_eq!(exists(&["ion".into(), "-s".into(), "emptyvar".into()], &shell), Ok(false));
+        shell.variables_mut().set("testvar", "foobar".to_string());
+        assert_eq!(exists(&["ion".into(), "-s".into(), "testvar".into()], &shell), Ok(true));
+        shell.variables_mut().remove_variable("testvar");
+        assert_eq!(exists(&["ion".into(), "-s".into(), "testvar".into()], &shell), Ok(false));
+        // also check that it doesn't trigger on arrays
+        let mut array = types::Array::new();
+        array.push("element".into());
+        shell.variables_mut().remove_variable("array");
+        shell.variables_mut().set("array", array);
+        assert_eq!(exists(&["ion".into(), "-s".into(), "array".into()], &shell), Ok(false));
+
+        // check `exists --fn`
+        let name_str = "test_function";
+        let name = small::String::from(name_str);
+        let mut args = Vec::new();
+        args.push(KeyBuf { name: "testy".into(), kind: Primitive::Str });
+        let mut statements = Vec::new();
+        statements.push(Statement::End);
+        let description: small::String = "description".into();
+
+        shell
+            .variables_mut()
+            .set(&name, Function::new(Some(description), name.clone(), args, statements));
+
+        assert_eq!(exists(&["ion".into(), "--fn".into(), name_str.into()], &shell), Ok(true));
+        shell.variables_mut().remove_variable(name_str);
+        assert_eq!(exists(&["ion".into(), "--fn".into(), name_str.into()], &shell), Ok(false));
+
+        // check invalid flags / parameters (should all be treated as strings and
+        // therefore succeed)
+        assert_eq!(exists(&["ion".into(), "--foo".into()], &shell), Ok(true));
+        assert_eq!(exists(&["ion".into(), "-x".into()], &shell), Ok(true));
+    }
 
-#[test]
-fn test_match_flag_argument() {
-    let shell = shell::Shell::library();
-
-    // we don't really care about the passed values, as long as both sited return
-    // the same value
-    assert_eq!(match_flag_argument('a', "ARRAY", &shell), array_var_is_not_empty("ARRAY", &shell));
-    assert_eq!(match_flag_argument('b', "binary", &shell), binary_is_in_path("binary", &shell));
-    assert_eq!(match_flag_argument('d', "path", &shell), path_is_directory("path"));
-    assert_eq!(match_flag_argument('f', "file", &shell), path_is_file("file"));
-    assert_eq!(match_flag_argument('s', "STR", &shell), string_var_is_not_empty("STR", &shell));
-
-    // Any flag which is not implemented
-    assert_eq!(match_flag_argument('x', "ARG", &shell), false);
-}
+    #[test]
+    fn test_match_flag_argument() {
+        let shell = Shell::library();
+
+        // we don't really care about the passed values, as long as both sited return
+        // the same value
+        assert_eq!(
+            match_flag_argument('a', "ARRAY", &shell),
+            array_var_is_not_empty("ARRAY", &shell)
+        );
+        assert_eq!(match_flag_argument('b', "binary", &shell), binary_is_in_path("binary", &shell));
+        assert_eq!(match_flag_argument('d', "path", &shell), path_is_directory("path"));
+        assert_eq!(match_flag_argument('f', "file", &shell), path_is_file("file"));
+        assert_eq!(match_flag_argument('s', "STR", &shell), string_var_is_not_empty("STR", &shell));
+
+        // Any flag which is not implemented
+        assert_eq!(match_flag_argument('x', "ARG", &shell), false);
+    }
 
-#[test]
-fn test_match_option_argument() {
-    let shell = shell::Shell::library();
+    #[test]
+    fn test_match_option_argument() {
+        let shell = Shell::library();
 
-    // we don't really care about the passed values, as long as both sited return
-    // the same value
-    assert_eq!(match_option_argument("fn", "FUN", &shell), array_var_is_not_empty("FUN", &shell));
+        // we don't really care about the passed values, as long as both sited return
+        // the same value
+        assert_eq!(
+            match_option_argument("fn", "FUN", &shell),
+            array_var_is_not_empty("FUN", &shell)
+        );
 
-    // Any option which is not implemented
-    assert_eq!(match_option_argument("foo", "ARG", &shell), false);
-}
+        // Any option which is not implemented
+        assert_eq!(match_option_argument("foo", "ARG", &shell), false);
+    }
 
-#[test]
-fn test_path_is_file() {
-    assert_eq!(path_is_file("testing/empty_file"), true);
-    assert_eq!(path_is_file("this-does-not-exist"), false);
-}
+    #[test]
+    fn test_path_is_file() {
+        assert_eq!(path_is_file("testing/empty_file"), true);
+        assert_eq!(path_is_file("this-does-not-exist"), false);
+    }
 
-#[test]
-fn test_path_is_directory() {
-    assert_eq!(path_is_directory("testing"), true);
-    assert_eq!(path_is_directory("testing/empty_file"), false);
-}
+    #[test]
+    fn test_path_is_directory() {
+        assert_eq!(path_is_directory("testing"), true);
+        assert_eq!(path_is_directory("testing/empty_file"), false);
+    }
 
-#[test]
-fn test_binary_is_in_path() {
-    let mut shell = shell::Shell::library();
-
-    // TODO: We should probably also test with more complex PATH-variables:
-    // TODO: multiple/:directories/
-    // TODO: PATH containing directories which do not exist
-    // TODO: PATH containing directories without read permission (for user)
-    // TODO: PATH containing directories without execute ("enter") permission (for
-    // user) TODO: empty PATH?
-    shell.set("PATH", "testing/".to_string());
-
-    assert_eq!(binary_is_in_path("executable_file", &shell), true);
-    assert_eq!(binary_is_in_path("empty_file", &shell), false);
-    assert_eq!(binary_is_in_path("file_does_not_exist", &shell), false);
-}
+    #[test]
+    fn test_binary_is_in_path() {
+        let mut shell = Shell::library();
+
+        // TODO: We should probably also test with more complex PATH-variables:
+        // TODO: multiple/:directories/
+        // TODO: PATH containing directories which do not exist
+        // TODO: PATH containing directories without read permission (for user)
+        // TODO: PATH containing directories without execute ("enter") permission (for
+        // user) TODO: empty PATH?
+        shell.variables_mut().set("PATH", "testing/".to_string());
+
+        assert_eq!(binary_is_in_path("executable_file", &shell), true);
+        assert_eq!(binary_is_in_path("empty_file", &shell), false);
+        assert_eq!(binary_is_in_path("file_does_not_exist", &shell), false);
+    }
 
-#[test]
-fn test_file_has_execute_permission() {
-    assert_eq!(file_has_execute_permission("testing/executable_file"), true);
-    assert_eq!(file_has_execute_permission("testing"), true);
-    assert_eq!(file_has_execute_permission("testing/empty_file"), false);
-    assert_eq!(file_has_execute_permission("this-does-not-exist"), false);
-}
+    #[test]
+    fn test_file_has_execute_permission() {
+        assert_eq!(file_has_execute_permission("testing/executable_file"), true);
+        assert_eq!(file_has_execute_permission("testing"), true);
+        assert_eq!(file_has_execute_permission("testing/empty_file"), false);
+        assert_eq!(file_has_execute_permission("this-does-not-exist"), false);
+    }
 
-#[test]
-fn test_string_is_nonzero() {
-    assert_eq!(string_is_nonzero("NOT ZERO"), true);
-    assert_eq!(string_is_nonzero(""), false);
-}
+    #[test]
+    fn test_string_is_nonzero() {
+        assert_eq!(string_is_nonzero("NOT ZERO"), true);
+        assert_eq!(string_is_nonzero(""), false);
+    }
 
-#[test]
-fn test_array_var_is_not_empty() {
-    let mut shell = shell::Shell::library();
+    #[test]
+    fn test_array_var_is_not_empty() {
+        let mut shell = Shell::library();
 
-    shell.variables_mut().set("EMPTY_ARRAY", types::Array::new());
-    assert_eq!(array_var_is_not_empty("EMPTY_ARRAY", &shell), false);
+        shell.variables_mut().set("EMPTY_ARRAY", types::Array::new());
+        assert_eq!(array_var_is_not_empty("EMPTY_ARRAY", &shell), false);
 
-    let mut not_empty_array = types::Array::new();
-    not_empty_array.push("array not empty".into());
-    shell.variables_mut().set("NOT_EMPTY_ARRAY", not_empty_array);
-    assert_eq!(array_var_is_not_empty("NOT_EMPTY_ARRAY", &shell), true);
+        let mut not_empty_array = types::Array::new();
+        not_empty_array.push("array not empty".into());
+        shell.variables_mut().set("NOT_EMPTY_ARRAY", not_empty_array);
+        assert_eq!(array_var_is_not_empty("NOT_EMPTY_ARRAY", &shell), true);
 
-    // test for array which does not even exist
-    shell.variables_mut().remove_variable("NOT_EMPTY_ARRAY");
-    assert_eq!(array_var_is_not_empty("NOT_EMPTY_ARRAY", &shell), false);
+        // test for array which does not even exist
+        shell.variables_mut().remove_variable("NOT_EMPTY_ARRAY");
+        assert_eq!(array_var_is_not_empty("NOT_EMPTY_ARRAY", &shell), false);
 
-    // array_var_is_not_empty should NOT match for non-array variables with the
-    // same name
-    shell.set("VARIABLE", "notempty-variable");
-    assert_eq!(array_var_is_not_empty("VARIABLE", &shell), false);
-}
+        // array_var_is_not_empty should NOT match for non-array variables with the
+        // same name
+        shell.variables_mut().set("VARIABLE", "notempty-variable");
+        assert_eq!(array_var_is_not_empty("VARIABLE", &shell), false);
+    }
 
-#[test]
-fn test_string_var_is_not_empty() {
-    let mut shell = shell::Shell::library();
+    #[test]
+    fn test_string_var_is_not_empty() {
+        let mut shell = Shell::library();
 
-    shell.set("EMPTY", "");
-    assert_eq!(string_var_is_not_empty("EMPTY", &shell), false);
+        shell.variables_mut().set("EMPTY", "");
+        assert_eq!(string_var_is_not_empty("EMPTY", &shell), false);
 
-    shell.set("NOT_EMPTY", "notempty");
-    assert_eq!(string_var_is_not_empty("NOT_EMPTY", &shell), true);
+        shell.variables_mut().set("NOT_EMPTY", "notempty");
+        assert_eq!(string_var_is_not_empty("NOT_EMPTY", &shell), true);
 
-    // string_var_is_not_empty should NOT match for arrays with the same name
-    let mut array = types::Array::new();
-    array.push("not-empty".into());
-    shell.variables_mut().set("ARRAY_NOT_EMPTY", array);
-    assert_eq!(string_var_is_not_empty("ARRAY_NOT_EMPTY", &shell), false);
+        // string_var_is_not_empty should NOT match for arrays with the same name
+        let mut array = types::Array::new();
+        array.push("not-empty".into());
+        shell.variables_mut().set("ARRAY_NOT_EMPTY", array);
+        assert_eq!(string_var_is_not_empty("ARRAY_NOT_EMPTY", &shell), false);
 
-    // test for a variable which does not even exist
-    shell.variables_mut().remove_variable("NOT_EMPTY");
-    assert_eq!(string_var_is_not_empty("NOT_EMPTY", &shell), false);
-}
+        // test for a variable which does not even exist
+        shell.variables_mut().remove_variable("NOT_EMPTY");
+        assert_eq!(string_var_is_not_empty("NOT_EMPTY", &shell), false);
+    }
 
-#[test]
-fn test_function_is_defined() {
-    use crate::lexers::assignments::{KeyBuf, Primitive};
-    let mut shell = shell::Shell::library();
-
-    // create a simple dummy function
-    let name_str = "test_function";
-    let name: small::String = name_str.into();
-    let mut args = Vec::new();
-    args.push(KeyBuf { name: "testy".into(), kind: Primitive::Str });
-    let mut statements = Vec::new();
-    statements.push(Statement::End);
-    let description: small::String = "description".into();
-
-    shell
-        .variables_mut()
-        .set(&name, Function::new(Some(description), name.clone(), args, statements));
-
-    assert_eq!(function_is_defined(name_str, &shell), true);
-    shell.variables_mut().remove_variable(name_str);
-    assert_eq!(function_is_defined(name_str, &shell), false);
+    #[test]
+    fn test_function_is_defined() {
+        use crate::lexers::assignments::{KeyBuf, Primitive};
+        let mut shell = Shell::library();
+
+        // create a simple dummy function
+        let name_str = "test_function";
+        let name: small::String = name_str.into();
+        let mut args = Vec::new();
+        args.push(KeyBuf { name: "testy".into(), kind: Primitive::Str });
+        let mut statements = Vec::new();
+        statements.push(Statement::End);
+        let description: small::String = "description".into();
+
+        shell
+            .variables_mut()
+            .set(&name, Function::new(Some(description), name.clone(), args, statements));
+
+        assert_eq!(function_is_defined(name_str, &shell), true);
+        shell.variables_mut().remove_variable(name_str);
+        assert_eq!(function_is_defined(name_str, &shell), false);
+    }
 }
diff --git a/src/lib/builtins/is.rs b/src/lib/builtins/is.rs
index c5f94fefc8f6694f1f9bfb3076c67c7ca93ae301..3cc785b657a64e56200869e32dc6fa7dda62f4d0 100644
--- a/src/lib/builtins/is.rs
+++ b/src/lib/builtins/is.rs
@@ -35,7 +35,7 @@ fn get_var_string(name: &str, shell: &mut Shell) -> types::Str {
         return "".into();
     }
 
-    match shell.variables().get::<types::Str>(&name[1..]) {
+    match shell.variables().get_str(&name[1..]) {
         Some(s) => s,
         None => "".into(),
     }
@@ -45,8 +45,8 @@ fn get_var_string(name: &str, shell: &mut Shell) -> types::Str {
 fn test_is() {
     fn vec_string(args: &[&str]) -> Vec<small::String> { args.iter().map(|&s| s.into()).collect() }
     let mut shell = Shell::library();
-    shell.set("x", "value");
-    shell.set("y", "0");
+    shell.variables_mut().set("x", "value");
+    shell.variables_mut().set("y", "0");
 
     // Four arguments
     assert_eq!(
diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs
index e94042ea8948677b303530dbee9af223aa1ecf20..a0ab28adb5cb4347253b9408ba0240b6b1e2421b 100644
--- a/src/lib/builtins/mod.rs
+++ b/src/lib/builtins/mod.rs
@@ -288,8 +288,7 @@ fn builtin_bool(args: &[small::String], shell: &mut Shell) -> i32 {
         return FAILURE;
     }
 
-    let opt =
-        if args[1].is_empty() { None } else { shell.variables().get::<types::Str>(&args[1][1..]) };
+    let opt = if args[1].is_empty() { None } else { shell.variables().get_str(&args[1][1..]) };
 
     match opt.as_ref().map(types::Str::as_str) {
         Some("1") => (),
diff --git a/src/lib/builtins/set.rs b/src/lib/builtins/set.rs
index 73983095e07cae83175eff6235db59f23babe28f..15115563147ec34e3b8b6b9cf5d5e500ce6f724e 100644
--- a/src/lib/builtins/set.rs
+++ b/src/lib/builtins/set.rs
@@ -59,20 +59,22 @@ pub fn set(args: &[small::String], shell: &mut Shell) -> i32 {
     match positionals {
         None => (),
         Some(kind) => {
-            let command = shell.variables().get::<types::Array>("args").unwrap()[0].clone();
-            // This used to take a `&[String]` but cloned them all, so although
-            // this is non-ideal and could probably be better done with `Rc`, it
-            // hasn't got any slower.
-            let arguments: types::Array =
-                iter::once(command).chain(args_iter.cloned().map(Value::Str)).collect();
-            match kind {
-                UnsetIfNone => {
-                    shell.variables_mut().set("args", arguments);
-                }
-                RetainIfNone => {
-                    if arguments.len() != 1 {
+            if let Some(Value::Array(array)) = shell.variables().get_ref("args") {
+                let command = array[0].clone();
+                // This used to take a `&[String]` but cloned them all, so although
+                // this is non-ideal and could probably be better done with `Rc`, it
+                // hasn't got any slower.
+                let arguments: types::Array =
+                    iter::once(command).chain(args_iter.cloned().map(Value::Str)).collect();
+                match kind {
+                    UnsetIfNone => {
                         shell.variables_mut().set("args", arguments);
                     }
+                    RetainIfNone => {
+                        if arguments.len() != 1 {
+                            shell.variables_mut().set("args", arguments);
+                        }
+                    }
                 }
             }
         }
diff --git a/src/lib/builtins/variables.rs b/src/lib/builtins/variables.rs
index 6bd4583569c41a90589f7d6ced5ca9bb7ad97157..d03c45b66dbb46e621b0b4cb8c2361f73bda74f7 100644
--- a/src/lib/builtins/variables.rs
+++ b/src/lib/builtins/variables.rs
@@ -151,7 +151,7 @@ mod test {
     struct VariableExpander<'a>(pub Variables<'a>);
 
     impl<'a> Expander for VariableExpander<'a> {
-        fn string(&self, var: &str) -> Option<types::Str> { self.0.get::<types::Str>(var) }
+        fn string(&self, var: &str) -> Option<types::Str> { self.0.get_str(var) }
     }
 
     // TODO: Rewrite tests now that let is part of the grammar.
diff --git a/src/lib/shell/assignments.rs b/src/lib/shell/assignments.rs
index 027dffc926eb042e20d4dd21a4c23781807754ca..6b5dba35614a475de05cd6b7313497c0b27a190c 100644
--- a/src/lib/shell/assignments.rs
+++ b/src/lib/shell/assignments.rs
@@ -7,7 +7,6 @@ use crate::{
     lexers::assignments::{Key, Operator, Primitive},
     parser::{assignments::*, is_valid_name},
     shell::variables::{EuclDiv, Modifications, OpError, Pow, Value},
-    types,
 };
 use std::{
     env,
@@ -81,7 +80,7 @@ impl<'b> Shell<'b> {
 
                 SUCCESS
             }
-            ExportAction::LocalExport(ref key) => match self.get::<types::Str>(key) {
+            ExportAction::LocalExport(ref key) => match self.variables.get_str(key) {
                 Some(var) => {
                     env::set_var(key, &*var);
                     SUCCESS
diff --git a/src/lib/shell/directory_stack.rs b/src/lib/shell/directory_stack.rs
index 0b677ff9849fd55b69736e6c4137d78e07f9ca4a..e8d22e47533b10046d3df1e9f7784a8e159817c4 100644
--- a/src/lib/shell/directory_stack.rs
+++ b/src/lib/shell/directory_stack.rs
@@ -207,7 +207,11 @@ impl DirectoryStack {
     /// variable,
     /// else it will return a default value of 1000.
     fn get_size(variables: &Variables) -> usize {
-        variables.get_str_or_empty("DIRECTORY_STACK_SIZE").parse::<usize>().unwrap_or(1000)
+        variables
+            .get_str("DIRECTORY_STACK_SIZE")
+            .unwrap_or_default()
+            .parse::<usize>()
+            .unwrap_or(1000)
     }
 
     /// Create a new `DirectoryStack` containing the current working directory,
diff --git a/src/lib/shell/flow.rs b/src/lib/shell/flow.rs
index b053ad6fabc40a4f2cb8e1666a1051ee2324b08c..131c0d346c513c036d05717eb5a23b572106b5c5 100644
--- a/src/lib/shell/flow.rs
+++ b/src/lib/shell/flow.rs
@@ -69,7 +69,7 @@ impl<'a> Shell<'a> {
             ($chunk:expr, $def:expr) => {
                 for (key, value) in variables.iter().zip($chunk.chain(::std::iter::repeat($def))) {
                     if key != "_" {
-                        self.set(key, value.clone());
+                        self.variables_mut().set(key, value.clone());
                     }
                 }
 
@@ -91,7 +91,7 @@ impl<'a> Shell<'a> {
             }
             ForValueExpression::Normal(value) => {
                 if &variables[0] != "_" {
-                    self.set(&variables[0], value.clone());
+                    self.variables_mut().set(&variables[0], value.clone());
                 }
 
                 match self.execute_statements(statements) {
@@ -242,7 +242,7 @@ impl<'a> Shell<'a> {
                     _ => (),
                 }
                 let previous_status = self.previous_status.to_string();
-                self.set("?", previous_status);
+                self.variables_mut().set("?", previous_status);
             }
             Statement::Break => return Condition::Break,
             Statement::Continue => return Condition::Continue,
@@ -306,15 +306,21 @@ impl<'a> Shell<'a> {
                 // let pattern_is_array = is_array(&value);
                 let previous_bind = case.binding.as_ref().and_then(|bind| {
                     if is_array {
-                        let out = self.variables.get::<types::Array>(bind).map(Value::Array);
-                        self.set(
+                        let out = if let Some(Value::Array(array)) =
+                            self.variables.get_ref(bind).cloned()
+                        {
+                            Some(Value::Array(array))
+                        } else {
+                            None
+                        };
+                        self.variables_mut().set(
                             &bind,
                             value.iter().cloned().map(Value::Str).collect::<types::Array>(),
                         );
                         out
                     } else {
-                        let out = self.variables.get::<types::Str>(bind).map(Value::Str);
-                        self.set(&bind, value.join(" "));
+                        let out = self.variables.get_str(bind).map(Value::Str);
+                        self.variables_mut().set(&bind, value.join(" "));
                         out
                     }
                 });
@@ -332,7 +338,7 @@ impl<'a> Shell<'a> {
                     if let Some(value) = previous_bind {
                         match value {
                             Value::HashMap(_) | Value::Array(_) | Value::Str(_) => {
-                                self.set(bind, value);
+                                self.variables_mut().set(bind, value);
                             }
                             _ => (),
                         }
@@ -382,7 +388,7 @@ fn expand_pipeline<'a>(
     let mut statements = Vec::new();
 
     while let Some(item) = item_iter.next() {
-        if let Some(alias) = shell.variables.get::<types::Alias>(item.command()) {
+        if let Some(Value::Alias(alias)) = shell.variables.get_ref(item.command()) {
             statements = StatementSplitter::new(alias.0.as_str())
                 .map(|stmt| parse_and_validate(stmt, &shell.builtins))
                 .collect();
diff --git a/src/lib/shell/fork.rs b/src/lib/shell/fork.rs
index 513edfcc7de6eacba93c385504ea795b6a450c2e..52a24bccabf419b707be70a652c1bb6d1ad53b13 100644
--- a/src/lib/shell/fork.rs
+++ b/src/lib/shell/fork.rs
@@ -123,7 +123,7 @@ impl<'a, 'b> Fork<'a, 'b> {
 
                 // Obtain ownership of the child's copy of the shell, and then configure it.
                 let mut shell: Shell = unsafe { (self.shell as *const Shell).read() };
-                shell.set("PID", sys::getpid().unwrap_or(0).to_string());
+                shell.variables_mut().set("PID", sys::getpid().unwrap_or(0).to_string());
 
                 // Execute the given closure within the child's shell.
                 child_func(&mut shell);
diff --git a/src/lib/shell/job.rs b/src/lib/shell/job.rs
index ae65eb86315e545ce4553d43c8bdb52c2f6a878d..a08d86ad1c0bc781b5b05db633aa2d2e010f1ec7 100644
--- a/src/lib/shell/job.rs
+++ b/src/lib/shell/job.rs
@@ -2,8 +2,7 @@ use super::Shell;
 use crate::{
     builtins::BuiltinFunction,
     parser::{pipelines::RedirectFrom, Expander},
-    shell::flow_control::Function,
-    types,
+    types, Value,
 };
 use std::{fmt, fs::File, str};
 
@@ -21,8 +20,9 @@ impl<'a> Job<'a> {
     /// Takes the current job's arguments and expands them, one argument at a
     /// time, returning a new `Job` with the expanded arguments.
     pub fn expand(&mut self, shell: &Shell) {
-        if shell.variables.get::<Function>(&self.args[0]).is_none() {
-            self.args = self.args.drain().flat_map(|arg| expand_arg(&arg, shell)).collect();
+        match shell.variables.get_ref(&self.args[0]) {
+            Some(Value::Function(_)) => {}
+            _ => self.args = self.args.drain().flat_map(|arg| expand_arg(&arg, shell)).collect(),
         }
     }
 
diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs
index e97779c97a40c5a715be697b09f834b0a3cede95..c55234ce1b569128936bca0b74ee7aee265df1e1 100644
--- a/src/lib/shell/mod.rs
+++ b/src/lib/shell/mod.rs
@@ -15,12 +15,12 @@ pub mod variables;
 pub(crate) use self::job::Job;
 use self::{
     directory_stack::DirectoryStack,
-    flow_control::{Block, Function, FunctionError, Statement},
+    flow_control::{Block, FunctionError, Statement},
     foreground::ForegroundSignals,
     fork::{Fork, IonResult},
     pipe_exec::{foreground, job_control::BackgroundProcess},
     status::*,
-    variables::{GetVariable, Variables},
+    variables::Variables,
 };
 pub use self::{fork::Capture, pipe_exec::job_control::ProcessState, variables::Value};
 use crate::{
@@ -265,12 +265,14 @@ impl<'a> Shell<'a> {
         name: &str,
         args: &[S],
     ) -> Result<i32, IonError> {
-        self.variables.get::<Function>(name).ok_or(IonError::DoesNotExist).and_then(|function| {
+        if let Some(Value::Function(function)) = self.variables.get_ref(name).cloned() {
             function
                 .execute(self, args)
                 .map(|_| self.previous_status)
                 .map_err(|err| IonError::Function { why: err })
-        })
+        } else {
+            Err(IonError::DoesNotExist)
+        }
     }
 
     /// A method for executing scripts in the Ion shell without capturing. Given a `Path`, this
@@ -316,19 +318,6 @@ impl<'a> Shell<'a> {
         }
     }
 
-    /// Gets any variable, if it exists within the shell's variable map.
-    pub fn get<T>(&self, name: &str) -> Option<T>
-    where
-        Variables<'a>: GetVariable<T>,
-    {
-        self.variables.get::<T>(name)
-    }
-
-    /// Sets a variable of `name` with the given `value` in the shell's variable map.
-    pub fn set<T: Into<Value<'a>>>(&mut self, name: &str, value: T) {
-        self.variables.set(name, value);
-    }
-
     /// Executes a pipeline and returns the final exit status of the pipeline.
     pub fn run_pipeline(&mut self, mut pipeline: Pipeline<'a>) -> Option<i32> {
         let command_start_time = SystemTime::now();
@@ -350,8 +339,8 @@ impl<'a> Shell<'a> {
                 Some(self.execute_pipeline(pipeline))
             }
         // Branch else if -> input == shell function and set the exit_status
-        } else if let Some(function) =
-            self.variables.get::<Function>(&pipeline.items[0].job.args[0])
+        } else if let Some(Value::Function(function)) =
+            self.variables.get_ref(&pipeline.items[0].job.args[0]).cloned()
         {
             if !pipeline.requires_piping() {
                 match function.execute(self, &pipeline.items[0].job.args) {
@@ -384,7 +373,7 @@ impl<'a> Shell<'a> {
 
         // Retrieve the exit_status and set the $? variable and history.previous_status
         if let Some(code) = exit_status {
-            self.set("?", code.to_string());
+            self.variables_mut().set("?", code.to_string());
             self.previous_status = code;
         }
 
diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs
index 2de0033462c444d43690e4d89ab77db2d2f20840..232a0dc4670945bf19e51964f36753fa623aad64 100644
--- a/src/lib/shell/pipe_exec/mod.rs
+++ b/src/lib/shell/pipe_exec/mod.rs
@@ -17,11 +17,11 @@ use self::{
     streams::{duplicate_streams, redirect_streams},
 };
 use super::{
-    flow_control::{Function, FunctionError},
+    flow_control::FunctionError,
     job::{Job, JobVariant, RefinedJob, TeeItem},
     signals::{self, SignalHandler},
     status::*,
-    Shell,
+    Shell, Value,
 };
 use crate::{
     builtins::{self, BuiltinFunction},
@@ -276,19 +276,23 @@ impl<'b> Shell<'b> {
     }
 
     fn exec_function<S: AsRef<str>>(&mut self, name: &str, args: &[S]) -> i32 {
-        match self.variables.get::<Function>(name).unwrap().execute(self, args) {
-            Ok(()) => SUCCESS,
-            Err(FunctionError::InvalidArgumentCount) => {
-                eprintln!("ion: invalid number of function arguments supplied");
-                FAILURE
-            }
-            Err(FunctionError::InvalidArgumentType(expected_type, value)) => {
-                eprintln!(
-                    "ion: function argument has invalid type: expected {}, found value \'{}\'",
-                    expected_type, value
-                );
-                FAILURE
+        if let Some(Value::Function(function)) = self.variables.get_ref(name).cloned() {
+            match function.execute(self, args) {
+                Ok(()) => SUCCESS,
+                Err(FunctionError::InvalidArgumentCount) => {
+                    eprintln!("ion: invalid number of function arguments supplied");
+                    FAILURE
+                }
+                Err(FunctionError::InvalidArgumentType(expected_type, value)) => {
+                    eprintln!(
+                        "ion: function argument has invalid type: expected {}, found value \'{}\'",
+                        expected_type, value
+                    );
+                    FAILURE
+                }
             }
+        } else {
+            unreachable!()
         }
     }
 
@@ -340,7 +344,7 @@ impl<'b> Shell<'b> {
                 &builtins::builtin_cd,
                 iter::once("cd".into()).chain(job.args).collect(),
             )
-        } else if self.variables.get::<Function>(&job.args[0]).is_some() {
+        } else if let Some(Value::Function(_)) = self.variables.get_ref(&job.args[0]) {
             RefinedJob::function(job.args)
         } else if let Some(builtin) = job.builtin {
             RefinedJob::builtin(builtin, job.args)
diff --git a/src/lib/shell/shell_expand.rs b/src/lib/shell/shell_expand.rs
index 1352cb5f1d2be07293ef202adb17a533e9d2e302..e327ad5d16a1a631d9089f2920fc7ea9e7130b34 100644
--- a/src/lib/shell/shell_expand.rs
+++ b/src/lib/shell/shell_expand.rs
@@ -38,43 +38,40 @@ impl<'a, 'b> Expander for Shell<'b> {
         if name == "?" {
             Some(types::Str::from(self.previous_status.to_string()))
         } else {
-            self.get::<types::Str>(name)
+            self.variables().get_str(name)
         }
     }
 
     /// Expand an array variable with some selection
     fn array(&self, name: &str, selection: &Select) -> Option<types::Args> {
-        if let Some(array) = self.variables.get::<types::Array>(name) {
-            match selection {
+        match self.variables.get_ref(name) {
+            Some(Value::Array(array)) => match selection {
                 Select::All => {
-                    return Some(types::Args::from_iter(
-                        array.iter().map(|x| format!("{}", x).into()),
-                    ))
-                }
-                Select::Index(ref id) => {
-                    return id
-                        .resolve(array.len())
-                        .and_then(|n| array.get(n))
-                        .map(|x| types::Args::from_iter(Some(format!("{}", x).into())));
+                    Some(types::Args::from_iter(array.iter().map(|x| format!("{}", x).into())))
                 }
+                Select::Index(ref id) => id
+                    .resolve(array.len())
+                    .and_then(|n| array.get(n))
+                    .map(|x| args![types::Str::from(format!("{}", x))]),
                 Select::Range(ref range) => {
-                    if let Some((start, length)) = range.bounds(array.len()) {
+                    range.bounds(array.len()).and_then(|(start, length)| {
                         if array.len() > start {
-                            return Some(
+                            Some(
                                 array
                                     .iter()
                                     .skip(start)
                                     .take(length)
                                     .map(|var| format!("{}", var).into())
                                     .collect(),
-                            );
+                            )
+                        } else {
+                            None
                         }
-                    }
+                    })
                 }
-                _ => (),
-            }
-        } else if let Some(hmap) = self.variables.get::<types::HashMap>(name) {
-            match selection {
+                _ => None,
+            },
+            Some(Value::HashMap(hmap)) => match selection {
                 Select::All => {
                     let mut array = types::Args::new();
                     for (key, value) in hmap.iter() {
@@ -90,17 +87,14 @@ impl<'a, 'b> Expander for Shell<'b> {
                             _ => (),
                         }
                     }
-                    return Some(array);
+                    Some(array)
                 }
                 Select::Key(key) => {
-                    return Some(args![format!(
-                        "{}",
-                        hmap.get(&*key).unwrap_or(&Value::Str("".into()))
-                    )]);
+                    Some(args![format!("{}", hmap.get(&*key).unwrap_or(&Value::Str("".into())))])
                 }
                 Select::Index(index) => {
                     use crate::ranges::Index;
-                    return Some(args![format!(
+                    Some(args![format!(
                         "{}",
                         hmap.get(&types::Str::from(
                             match index {
@@ -110,12 +104,11 @@ impl<'a, 'b> Expander for Shell<'b> {
                             .to_string()
                         ))
                         .unwrap_or(&Value::Str("".into()))
-                    )]);
+                    )])
                 }
-                _ => (),
-            }
-        } else if let Some(bmap) = self.variables.get::<types::BTreeMap>(name) {
-            match selection {
+                _ => None,
+            },
+            Some(Value::BTreeMap(bmap)) => match selection {
                 Select::All => {
                     let mut array = types::Args::new();
                     for (key, value) in bmap.iter() {
@@ -131,17 +124,14 @@ impl<'a, 'b> Expander for Shell<'b> {
                             _ => (),
                         }
                     }
-                    return Some(array);
+                    Some(array)
                 }
                 Select::Key(key) => {
-                    return Some(args![format!(
-                        "{}",
-                        bmap.get(&*key).unwrap_or(&Value::Str("".into()))
-                    )]);
+                    Some(args![format!("{}", bmap.get(&*key).unwrap_or(&Value::Str("".into())))])
                 }
                 Select::Index(index) => {
                     use crate::ranges::Index;
-                    return Some(args![format!(
+                    Some(args![format!(
                         "{}",
                         bmap.get(&types::Str::from(
                             match index {
@@ -151,12 +141,12 @@ impl<'a, 'b> Expander for Shell<'b> {
                             .to_string()
                         ))
                         .unwrap_or(&Value::Str("".into()))
-                    )]);
+                    )])
                 }
-                _ => (),
-            }
+                _ => None,
+            },
+            _ => None,
         }
-        None
     }
 
     fn map_keys(&self, name: &str, sel: &Select) -> Option<types::Args> {
@@ -195,9 +185,7 @@ impl<'a, 'b> Expander for Shell<'b> {
         match tilde_prefix {
             "" => sys_env::home_dir().map(|home| home.to_string_lossy().to_string() + rest),
             "+" => Some(env::var("PWD").unwrap_or_else(|_| "?".to_string()) + rest),
-            "-" => {
-                self.variables.get::<types::Str>("OLDPWD").map(|oldpwd| oldpwd.to_string() + rest)
-            }
+            "-" => self.variables.get_str("OLDPWD").map(|oldpwd| oldpwd.to_string() + rest),
             _ => {
                 let (neg, tilde_num) = if tilde_prefix.starts_with('+') {
                     (false, &tilde_prefix[1..])
diff --git a/src/lib/shell/variables/mod.rs b/src/lib/shell/variables/mod.rs
index 9b141d1a94ae1bc38cc23440c3c56c7b30666adf..9c610e94292740e4870fedd925229a161b32685e 100644
--- a/src/lib/shell/variables/mod.rs
+++ b/src/lib/shell/variables/mod.rs
@@ -81,26 +81,6 @@ type_from_value!(Function<'a> : Function else
     )
 );
 
-macro_rules! eq {
-    ($lhs:ty : $variant:ident) => {
-        impl<'a> PartialEq<Value<'a>> for $lhs {
-            fn eq(&self, other: &Value<'a>) -> bool {
-                match other {
-                    Value::$variant(ref inner) => inner == self,
-                    _ => false,
-                }
-            }
-        }
-    };
-}
-
-eq!(types::Str: Str);
-eq!(types::Alias: Alias);
-eq!(types::Array<'a>: Array);
-eq!(types::HashMap<'a>: HashMap);
-eq!(types::BTreeMap<'a>: BTreeMap);
-eq!(Function<'a>: Function);
-
 impl<'a> Eq for Value<'a> {}
 
 // this one’s only special because of the lifetime parameter
@@ -257,7 +237,7 @@ impl<'a> Variables<'a> {
     /// Useful for getting smaller prompts, this will produce a simplified variant of the
     /// working directory which the leading `HOME` prefix replaced with a tilde character.
     fn get_simplified_directory(&self) -> types::Str {
-        let home = self.get::<types::Str>("HOME").unwrap_or_else(|| "?".into());
+        let home = self.get_str("HOME").unwrap_or_else(|| "?".into());
         env::var("PWD").unwrap().replace(&*home, "~").into()
     }
 
@@ -269,10 +249,6 @@ impl<'a> Variables<'a> {
         c.is_alphanumeric() || c == '_' || c == '?' || c == '.' || c == '-' || c == '+'
     }
 
-    pub fn get_str_or_empty(&self, name: &str) -> types::Str {
-        self.get::<types::Str>(name).unwrap_or_default()
-    }
-
     pub fn remove_variable(&mut self, name: &str) -> Option<Value<'a>> {
         if name.starts_with("super::") || name.starts_with("global::") {
             // Cannot mutate outer namespace
@@ -289,6 +265,42 @@ impl<'a> Variables<'a> {
         self.0.get_mut(name)
     }
 
+    pub fn get_str(&self, name: &str) -> Option<types::Str> {
+        match name {
+            "MWD" => return Some(self.get_minimal_directory()),
+            "SWD" => return Some(self.get_simplified_directory()),
+            _ => (),
+        }
+        // If the parsed name contains the '::' pattern, then a namespace was
+        // designated. Find it.
+        match name.find("::").map(|pos| (&name[..pos], &name[pos + 2..])) {
+            Some(("c", variable)) | Some(("color", variable)) => {
+                Colors::collect(variable).into_string().map(|s| s.into())
+            }
+            Some(("x", variable)) | Some(("hex", variable)) => {
+                match u8::from_str_radix(variable, 16) {
+                    Ok(c) => Some((c as char).to_string().into()),
+                    Err(why) => {
+                        eprintln!("ion: hex parse error: {}: {}", variable, why);
+                        None
+                    }
+                }
+            }
+            Some(("env", variable)) => env::var(variable).map(Into::into).ok().map(|s| s),
+            Some(("super", _)) | Some(("global", _)) | None => {
+                // Otherwise, it's just a simple variable name.
+                match self.get_ref(name) {
+                    Some(Value::Str(val)) => Some(val.clone()),
+                    _ => env::var(name).ok().map(|s| s.into()),
+                }
+            }
+            Some((..)) => {
+                eprintln!("ion: unsupported namespace: '{}'", name);
+                None
+            }
+        }
+    }
+
     pub fn get_ref(&self, mut name: &str) -> Option<&Value<'a>> {
         const GLOBAL_NS: &str = "global::";
         const SUPER_NS: &str = "super::";
@@ -310,13 +322,6 @@ impl<'a> Variables<'a> {
         };
         self.0.get_ref(name, namespace)
     }
-
-    pub fn get<T>(&self, name: &str) -> Option<T>
-    where
-        Self: GetVariable<T>,
-    {
-        GetVariable::<T>::get(self, name)
-    }
 }
 
 impl<'a> Default for Variables<'a> {
@@ -365,71 +370,6 @@ impl<'a> Default for Variables<'a> {
     }
 }
 
-pub trait GetVariable<T> {
-    fn get(&self, name: &str) -> Option<T>;
-}
-
-impl<'a> GetVariable<types::Str> for Variables<'a> {
-    fn get(&self, name: &str) -> Option<types::Str> {
-        use crate::types::Str;
-
-        match name {
-            "MWD" => return Some(Str::from(Value::Str(self.get_minimal_directory()))),
-            "SWD" => return Some(Str::from(Value::Str(self.get_simplified_directory()))),
-            _ => (),
-        }
-        // If the parsed name contains the '::' pattern, then a namespace was
-        // designated. Find it.
-        match name.find("::").map(|pos| (&name[..pos], &name[pos + 2..])) {
-            Some(("c", variable)) | Some(("color", variable)) => {
-                Colors::collect(variable).into_string().map(|s| Str::from(Value::Str(s.into())))
-            }
-            Some(("x", variable)) | Some(("hex", variable)) => {
-                match u8::from_str_radix(variable, 16) {
-                    Ok(c) => Some(Str::from(Value::Str((c as char).to_string().into()))),
-                    Err(why) => {
-                        eprintln!("ion: hex parse error: {}: {}", variable, why);
-                        None
-                    }
-                }
-            }
-            Some(("env", variable)) => {
-                env::var(variable).map(Into::into).ok().map(|s| Str::from(Value::Str(s)))
-            }
-            Some(("super", _)) | Some(("global", _)) | None => {
-                // Otherwise, it's just a simple variable name.
-                match self.get_ref(name) {
-                    Some(Value::Str(val)) => Some(Str::from(Value::Str(val.clone()))),
-                    _ => env::var(name).ok().map(|s| Str::from(Value::Str(s.into()))),
-                }
-            }
-            Some((..)) => {
-                eprintln!("ion: unsupported namespace: '{}'", name);
-                None
-            }
-        }
-    }
-}
-
-macro_rules! get_var {
-    ($types:ty, $variant:ident($inner:ident) => $ret:expr) => {
-        impl<'a> GetVariable<$types> for Variables<'a> {
-            fn get(&self, name: &str) -> Option<$types> {
-                match self.get_ref(name) {
-                    Some(Value::$variant($inner)) => Some($ret.clone()),
-                    _ => None,
-                }
-            }
-        }
-    };
-}
-
-get_var!(types::Alias, Alias(alias) => (*alias));
-get_var!(types::Array<'a>, Array(array) => array);
-get_var!(types::HashMap<'a>, HashMap(hmap) => hmap);
-get_var!(types::BTreeMap<'a>, BTreeMap(bmap) => bmap);
-get_var!(Function<'a>, Function(func) => func);
-
 #[cfg(test)]
 mod trait_test;
 
@@ -442,7 +382,7 @@ mod tests {
     struct VariableExpander<'a>(pub Variables<'a>);
 
     impl<'a> Expander for VariableExpander<'a> {
-        fn string(&self, var: &str) -> Option<types::Str> { self.0.get::<types::Str>(var) }
+        fn string(&self, var: &str) -> Option<types::Str> { self.0.get_str(var) }
     }
 
     #[test]
@@ -467,7 +407,7 @@ mod tests {
         env::set_var("PWD", "/var/log/nix");
         assert_eq!(
             types::Str::from("v/l/nix"),
-            variables.get::<types::Str>("MWD").expect("no value returned"),
+            variables.get_str("MWD").expect("no value returned"),
         );
     }
 
@@ -478,7 +418,7 @@ mod tests {
         env::set_var("PWD", "/var/log");
         assert_eq!(
             types::Str::from("/var/log"),
-            variables.get::<types::Str>("MWD").expect("no value returned"),
+            variables.get_str("MWD").expect("no value returned"),
         );
     }
 }