Search code examples
rustiotraits

How to return `impl Display` from a function


I'm struggling to understand how to properly implement alternative displays or traits of any kind for a struct.

struct Fraction {
  numerator: u32,
  denominator: u32
}

impl Fraction {
  fn unicode(&self) -> impl Display {
   // should I use named function or Closure?
   // Where does f come from?
   // can I re-use self?
   // How can I implement a trait that has multiple required functions or `type Output =`?
    fn fmt(&self, f: std::fmt::Formatter) -> std::fmt::Result {
       write!("{}⁄{}", self.numerator, self.denominator)
    } // <- this returns `()`
  }
}

This doesn't work, because fn fmt - as a function definition - doesn't return anything. Using an unnamed closure:

impl Fraction {
  fn unicode(&self) -> impl Display {
   // should I use named function or Closure?
   // Where does f come from?
   // can I re-use self?
   // How can I implement a trait that has multiple required functions or `type Output =`?
    |s: &Self, f: std::fmt::Formatter| -> std::fmt::Result {
       write!("{}⁄{}", self.numerator, self.denominator)
    } // <- this returns `()`
  }
}

// somewhere else:
impl Display for Fraction {
  fn fmt(&self, f: std::fmt::Formatter) -> std::fmt::Result {
    write!("{}/{}", self.numerator, self.denominator)
    //        ^-- ASCII '/'
  }
}

says:

error[E0277]: `{closure@src/fraction/generic_fraction.rs:422:9: 422:57}` doesn't implement `std::fmt::Display`
   --> src/fraction/generic_fraction.rs:418:39
    |
418 |     fn display_mixed<'a>(&'a self) -> impl 'a + fmt::Display 
    |                                       ^^^^^^^^^^^^^^^^^^^^^^ `{closure@src/fraction/generic_fraction.rs:422:9: 422:57}` cannot be formatted with the default formatter
    |
    = help: the trait `std::fmt::Display` is not implemented for closure `{closure@src/fraction/generic_fraction.rs:422:9: 422:57}`
    = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

similar questions:

As opposed to returning an iterator (most docs I've found are about that), I'm trying to implement my trait in the function block. Hencewhy I couldn't find documentation. Maybe this is the wrong way of doing it/thinking about it?


Solution

  • As @cafce25 points out in the comments, you must return something that implements the trait, which in Rust means you need to have an instance of a type that implements the trait. But you can still "hide" this from the signature and the exported types, for example by declaring the type within the function:

    fn foo() -> impl std::fmt::Display {
        struct Example;
        impl std::fmt::Display for Example {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "hi")
            }
        }
        Example
    }
    
    fn main() {
        let d = foo();
        println!("{d}"); // prints "hi"
    }
    

    The returned instance must carry enough data with it that its Display implementation can actually function. In the Example above, there is no data (Example is actually a ZST), because the fmt implementation simply writes a constant string to the formatter.

    More realistically though, you will need some data for the implementation, e.g. for your Fraction:

    #[derive(Clone)]
    struct Fraction {
        numerator: u32,
        denominator: u32
    }
    
    impl Fraction {
        fn unicode(&self) -> impl std::fmt::Display {
            struct FractionAsUnicode(Fraction);
            impl std::fmt::Display for FractionAsUnicode {
                fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                    write!(f, "{}⁄{}", self.0.numerator, self.0.denominator)
                }
            }
            FractionAsUnicode(self.clone())
        }
    }
    

    If you want to avoid the clone and only capture the original fraction by shared reference (because the resulting object only needs to exist for the duration of a print call), you can do this instead:

    fn unicode(&self) -> impl std::fmt::Display + '_ {
        struct FractionAsUnicode<'a>(&'a Fraction);
        impl<'a> std::fmt::Display for FractionAsUnicode<'a> {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "{}⁄{}", self.0.numerator, self.0.denominator)
            }
        }
        FractionAsUnicode(self)
    }
    

    Here we declared the returned "hidden" object with a lifetime 'a (being the lifetime of the original Fraction reference given to the unicode function), which we must also spell out in the impl block. This is standard for structs with references. The slightly surprising thing is the extra + '_ added to the return type. Without this, the compiler complains: "hidden type for impl std::fmt::Display captures lifetime that does not appear in bounds". It is saying that, even though we don't actually spell out the lifetimes in the unicode signature, there is a connection between the lifetime of the returned instance and that of the original Fraction instance, namely, that the returned instance cannot outlive the original instance.

    Short answers to the questions you pose in the comments:

    • What about multiple functions or associated types, e.g. -> impl Iterator<Item = i32>? Since you have to have a full impl block for the trait, you can declare all the functions and the associated types as usual.
    • Can I re-use self? No, as seen above, you need to capture a reference, clone the data, or extract some data you need into the returned instance.

    Finally, let me point out that -> impl SomeTrait does not mean this function returns a trait object (as in -> Box<dyn SomeTrait>). The function cannot, for example, return different implementations of SomeTrait from different branches. The syntax simply means that there exists a single type that implements the trait, but you won't see which type that is from the signature.