How to implement Display that requires dynamic context?


#1

Hi. This is a question about how to use the {} format with values that require some external context for their rendering.

We all know about the Display trait which is used for the {} format. However it did not work for my case. I am handling interned strings which are represented as completely independent u32 values. The actual string values are stored in some pool of interned strings. Obviously, such ‘strings’ cannot be meaningfully passed to format!("String: {}", interned_string);. However, what we can do is to convert the unprintable value into something printable. For the case of interned strings we can retrieve the string out of the pool: format!("String: {}", pool.get(interned_string));.

But what if a have a collection of such directly-unformattable values and want to format it in a generic way?

For example, if I have a custom format like this:

struct MySlice<'a, T: 'a>(&'a [T]);

impl<'a, T> fmt::Display for MySlice<'a, T> where T: fmt::Display {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        try!(write!(f, "("));
        if self.0.len() >= 1 {
            try!(write!(f, "{}", self.0[0]));
            for item in &self.0[1..] {
                try!(write!(f, ", {}", item));
            }
        }
        try!(write!(f, ")"));
        Ok(())
    }
}

How do I implement a similar generic thing for Ts that are not readily Displayable and require some dynamic context? Like the interned strings that cannot be formatted without their pool, an so is a slice of interned strings.


#2

You’d normally do this with a display adapter wrapper struct. For example:

struct NotDisplayable;

struct Context;

struct Display<'a> {
	item: &'a NotDisplayable,
	context: &'a Context,
}

impl<'a> fmt::Display for Display<'a> {
	// ...
}

impl NotDisplayable {
	pub fn display<'a>(&'a self, context: &'a Context) -> Display<'a> {
		Display { 
			item: self,
			context: context,
		}
	}
}

And then you can use it like so:

let context: Context = /* ... */;
let collection: Vec<NotDisplayable> = /* ... */;
let display_iter = collection.iter().map(|el|el.display(&context));

std’s Path uses such a mechanism: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.display


#3

Hm. So the trick is still in using some displayable struct that wraps undisplayable stuff, but for collections one would like to wrap not the collection type itself, but an iterator over it, thus reducing work. With this approach it is not necessary to convert Vec<NotDisplayable> into owned Vec<Display> to display it. We do this only for some Iterator<Item=&NotDisplayable>, wrapping it into some Iterator<Item=Display> which can be used then to format the vector in whatever way necessary. Heh, brillinant! Thank you, @jethrogb

I’m also quite interested in the approach taken by the IntoIterator trait. I find it curious how its into_iter() method consumes Self to return an iterator. This trait can be implemented for references to container types, not the containers themselves, with such implementations yielding iterators over references to the elements of the referenced container. I guess this simplifies wrapper implementations like that struct Display which keeps a reference inside it.