Search code examples
stringunit-testingrustlanguage-design

use the format! macro with something of type Box<dyn Display + 'static>


I am trying to write unit-tests for a language I am currently writing, so I am working on storing and retrieving variables in the parser except I get the error

error[E0277]: the size for values of type `dyn std::fmt::Display` cannot be known at compilation time
   --> parser/src/env.rs:48:33
    |
48  |         assert_eq!(String::from(format!("{}", *get_binding("test", &env).unwrap().get())), "hello".to_string())
    |                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-
time
    |
    = help: the trait `Sized` is not implemented for `dyn std::fmt::Display`
note: required by a bound in `ArgumentV1::<'a>::new`
   --> /home/muppi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:314:20
    |
314 |     pub fn new<'b, T>(x: &'b T, f: fn(&T, &mut Formatter<'_>) -> Result) -> ArgumentV1<'b> {
    |                    ^ required by this bound in `ArgumentV1::<'a>::new`
    = note: this error originates in the macro `$crate::__export::format_args` (in Nightly builds, run with -Z macro-backtrace for m
ore info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `parser` due to previous error

my code

pub mod env {
    use crate::vars::vars::*;
    use std::{collections::HashMap, fmt::Display};

    #[derive(Default)]
    pub struct Environ {
        pub bindings: HashMap<String, Var<Box<dyn Display + 'static>>>,
    }
    #[allow(dead_code)]
    pub(crate) fn store_var(name: &str, var: Var<Box<dyn Display + 'static>>, env: &mut Environ) {
        env.bindings.insert(String::from(name), var);
    }
    #[allow(dead_code)]
    pub(crate) fn get_binding<'a>(name: &'a str, env: &'a Environ) -> Result<&'a Var<Box<dyn Display>>, String> {
        if env.bindings.contains_key(&name.to_string()) {
            let val = env.bindings.get(&name.to_string());
            Ok(val.unwrap())
        } else {
            Err("Key does not exist".to_string())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::env::{Environ, store_var, *};
    use crate::vars::vars::*;
    
    #[test]
    fn store_binding() {
        let mut env = Environ::default();
        store_var("test", Var::<i32>::new(Box::new(5), None), &mut env);
        assert_eq!(env.bindings.len(), 1)
    }
    #[test]
    #[should_panic(expected="value not found")]
    fn retrieve_non_existent_binding() {
        let mut env = Environ::default();
        store_var("test", Var::<&str>::new(Box::new("hello"), None), &mut env);
        if get_binding("this_does_exist", &env).is_err() {
            panic!("value not found")
        }
    }
    #[test]
    fn retrieve_binding() {
        let mut env = Environ::default();
        store_var("test", Var::<&str>::new(Box::new("hello"), None), &mut env);
        assert_eq!(String::from(format!("{}", *get_binding("test", &env).unwrap().get())), "hello".to_string())
    }
}

The last test is where the error occurs. This test is meant to use the format! macro and format the string to make a string which contains the data stored in the variable. The var lib

pub mod vars {
    use std::fmt;

    #[derive(Debug, Clone, PartialEq)]
    pub struct Var<T> {
        pub val: Box<T>,
        pub desc: Option<String>,
    }

    impl<T> std::ops::Deref for Var<T> {
        type Target = Box<T>;
    
        fn deref(&self) -> &Self::Target {
            &self.val
        }
    }
    impl<T> Var<T> {
        pub fn new<Y>(val: Y, desc: Option<&str>) -> Var<Y> {
            if let Some(desc) = desc {
                Var {val: Box::new(val), desc: Some(desc.to_string())}
            } else {
                Var {val: Box::new(val), desc: None}
            }
        }
        pub fn get(self) -> T {
            *self.val
        }
    }
    impl<T> fmt::Display for Var<T>
    where
        T: fmt::Display,
    {
        fn fmt(self: &Var<T>, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{}", *self.val)
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::vars::vars::*;

    #[test]
    fn new_var() {
        assert_eq!(
            Var::<i32>::new(10, Some("hello_world")),
            Var {
                val: Box::new(10),
                desc: Some("hello_world".to_string())
            }
        )
    }
}


Solution

  • The problem is not in the display, it's that you cannot call get() on the &Var returned by get_binding(). For example, this doesn't compile:

    let x = get_binding("test", &env).unwrap().get();
    

    This is because get_binding() returns a reference to Var, whereas Var::get() takes self by value (and consumes it). Your attempt to fix the issue by adding a * dereference only led you to a new type error which obscured the actual issue and the correct way to address it.

    Once the root cause is apparent, the obvious fix is to change get() so it takes &self and returns a reference, making it compatible with the reference returned by get_binding(). For example:

    pub fn get(&self) -> &T {
        &self.val
    }
    

    With this change, your test compiles and passes.