How can I handle Option and non-Option types in a function that requires Display?

I have a generic function with a trait bound on Display

fn display_all<T>(items: BTreeMap<T, i32>)
where T: std::fmt::Display

Now I'd like to pass Option and non-Option types to it. But of course, Option doesn't implement Display, so this doesn't work.

let mut non_opt = BTreeMap::new();
non_opt.insert("One", 1);
  
let mut opt = BTreeMap::new();
opt.insert(Some("Two".to_string()), 2);
  
display_all(non_opt);
display_all(opt);

Ideally, a default value should be used if a value is none. I'm unsure wheter the default value should be provided by the caller or the callee, but I'm currently leaning towards the latter. I've tried a wrapper trait, but this also doesn't work: "conflicting implementations of trait DisplayOption for type std::option::Option<_>"

pub trait DisplayOption {
    fn to_string(&self) -> String;
}

impl<T: std::fmt::Display> DisplayOption for T {
    fn to_string(&self) -> String {
        std::string::ToString::to_string(&self)
    }
}

impl<T: std::fmt::Display> DisplayOption for Option<T> {
    fn to_string(&self) -> String {
        match self {
            Some(t) => std::string::ToString::to_string(&t),
            None => "None".to_string(),
        }
    }
}

If I remove the implementation for T the code works. It's just that now I need to implement Display and DisplayOption for the types I want to pass to the function, which creates annoying boilerplate. So I'd like to know if there is a better solution.

What's a good way to proceed here?

I don't know the use-case for this, but using Option for key in BTreeMap seems kind of wasteful, since key must be unique, so in this case there will be only one key/value pair with default value for Option as key. So user could just use non-option map and call inserts like this: non_opt.insert(some_option.unwrap_or_default(), value);

The use case for this is I'm getting data from the GitLab API and then want to group it by a certain criteria, i.e. Labels on an issue. But sometimes a issue doesn't have a label and that is where the key would be None. The i32 is just a simplified example, in my application the BTreeMap is actually a BTreeMap<T, Vec<_>>.

Why do you need this implementation? If you remove it it should work.

Regarding implementing Display trait for structs that do not implement it and are defined in different modules, i would also be interested if there is some nice trick for it.

What i currently do is. I create an easy to use wrapper struct and use it in calls. It is inconvenient, but i have not come up with better solution for now. The code is something along the lines:

struct DisplayWrapper<'a>(pub &'a BTreeMap<_, _>);

impl Display for DisplayWrapper {}

// Then in code:
println!("{}", DisplayWrapper(&your_tree));

You can generalize it for some cases.

Yes, if I remove it the code works. It's just that now I need to implement Display and DisplayOption for the types I want to pass to the function, which creates annoying boilerplate. So I'd like to know if there is a better solution.

Oh sorry. I thought you only wanted to pass Option to the function.

Do you specifically need the Display trait? Otherwise you could use the Debug trait instead.

Technically no, but isn't it bad practices to use Debug print for user facing text?

And I would like to take the Some value and use a default value for None values

The error with your wrapper trait is that the two impls

  • impl<T: Display> DisplayOption for T, when T = Option<Foo>, and
  • impl<T: Display> DisplayOption for Option<T>, when T = Foo

(for some Foo: Display) collide if Option<Foo>: Display (required by the trait bound in the first impl). Of course, this impl doesn't exist, but the coherence checker enforces std's right to add it in a future semver-compatible update.


To allow accepting both types, you'll have to "merge" Option<T> and T differently. The simple way is to just wrap the Ts in Some, but that would require reallocating the entire BTreeMap.

Instead, you can convert Option<T> to a wrapper type that's Display. Make display_all<T: Display> accept an IntoIterator<Item = (T, &i32)>.[1] Then use (edited so it actually works)

let non_optional: BTreeMap<String, i32>;
// This call uses T = &String
display_all(&non_optional);

use std::fmt::{self, Display};

struct DisplayOption<T>(Option<T>);
impl<T: Display> Display for DisplayOption<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(x) = &self.0 {
            x.fmt(f)
        } else {
            write!(f, "None")
        }
    }
}

let optional: BTreeMap<Option<String>, i32>;
// This call uses T = DisplayOption<&String>
display_all(optional.iter().map(|(k, v)| (DisplayOption(k.as_ref()), v)));

You can also use display_all<T: Display>(impl IntoIterator<Item = (Option<T>, i32)>), but you'll need an Iterator::map on both the optional and non-optional uses, and the responsibility for determining the display value of None falls on display_all.


  1. &T: Display where T: Display, so T doesn't need a reference ↩︎

3 Likes

Not sure if I'm doing this right, but I get a compiler error

error[E0308]: mismatched types
 --> src/main.rs:8:20
  |
8 |             if let Some(x) = self {
  |                    ^^^^^^^   ---- this expression has type `&DisplayOption<T>`
  |                    |
  |                    expected `DisplayOption<T>`, found `Option<_>`
  |
  = note: expected struct `DisplayOption<T>`
               found enum `Option<_>`

error[E0599]: the method `fmt` exists for mutable reference `&mut Formatter<'_>`, but its trait bounds were not satisfied
   --> src/main.rs:9:19
    |
  9 |                 f.fmt(x)
    |                   ^^^ method cannot be called on `&mut Formatter<'_>` due to unsatisfied trait bounds
    |
   ::: /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:566:1
    |
566 | pub struct Formatter<'a> {
    | ------------------------ doesn't satisfy `Formatter<'_>: std::fmt::Display`
    |
    = note: the following trait bounds were not satisfied:
            `Formatter<'_>: std::fmt::Display`
            which is required by `&mut Formatter<'_>: std::fmt::Display`
    = help: items from traits can only be used if the trait is in scope
help: trait `Pointer` which provides `fmt` is implemented but not in scope; perhaps you want to import it
    |
  1 + use std::fmt::Pointer;
    |

Playground

fixed Rust Playground

Nope, that’s my fault. (This is what I get from coding on my phone and not testing…) It should be:

use std::fmt::{self, Display};

struct DisplayOption<T>(Option<T>);
impl<T: Display> Display for DisplayOption<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(x) = &self.0 {
            x.fmt(f)
        } else {
            write!(f, "None")
        }
    }
}

Full playground, where I also define a proper signature for display_all.

What about this?