0

I have some data I'd like to prevent from logging (PrivateData), even on accident. But I'd still like to accept either those values or values with the Display trait (mostly strings but also a few Enum) in a function and struct. I tried implementing Into for Display and my PrivateDisplay traits, but it's reporting that it doesn't have a size at compile time. Is there a way to work around that or another approach that could work for this use case? I'd like to avoid requiring the programmer to wrap the types manually (e.g., create_data(Value::PrivateDisplay(PrivateData(Box::new(String::new("test")))))).

Code (playground)

use std::fmt;
use std::io::Read;

trait PrivateDisplay {
    fn private_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
}

struct PrivateData(String);

impl PrivateDisplay for PrivateData {
    fn private_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

enum Value {
    Display(Box<dyn fmt::Display>),
    PrivateDisplay(Box<dyn PrivateDisplay>),
}

impl Into<Value> for dyn fmt::Display {
    fn into(self) -> Value {
        Value::Display(Box::new(self))
    }
}

impl Into<Value> for dyn PrivateDisplay {
    fn into(self) -> Value {
        Value::PrivateDisplay(Box::new(self))
    }
}

struct Data {
    value: Value,
}

fn create_data<V>(value: V) -> Data
where
    V: Into<Value>
{
    Data {
        value: value,
    }
}

fn main() {
    create_data("test");
    create_data(PrivateData(String::from("test")));
    
    // May also be data provided from outside program
    let mut buffer = String::from("buffer");
    std::io::stdin().read_to_string(&mut buffer).unwrap();
    create_data(buffer);
}

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the size for values of type `(dyn std::fmt::Display + 'static)` cannot be known at compilation time
   --> src/main.rs:20:6
    |
21  | impl Into<Value> for dyn fmt::Display {
    |      ^^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `(dyn std::fmt::Display + 'static)`

error[E0277]: the size for values of type `(dyn PrivateDisplay + 'static)` cannot be known at compilation time
   --> src/main.rs:26:6
    |
27  | impl Into<Value> for dyn PrivateDisplay {
    |      ^^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `(dyn PrivateDisplay + 'static)`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`

To learn more, run the command again with --verbose.
0b10011
  • 18,397
  • 4
  • 65
  • 86
  • Does changing your `dyn` to a reference work? `impl Into for &dyn fmt::Display` – BallpointBen Aug 23 '21 at 18:05
  • @BallpointBen that adds a bunch of lifetime requirements that I can't seem to get to work. – 0b10011 Aug 23 '21 at 18:21
  • 2
    I think what you really want is something like `impl From for Value { fn from(t: T) -> Self { Value::Display(Box::new(t)) } }`, but that would cause conflicting implementations when you implement the version for `PrivateDisplay`. It's conflicting because it's possible for someone else to write a new type that implements both `fmt::Display` and `PrivateDisplay`, in which case it isn't possible to tell which conversion to use. [Rust playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f556ce66898176c572ec9e4c8db61b44). – Bernard Aug 23 '21 at 18:31
  • @Bernard Could you explain why `impl From ` does something different from `impl From `? [I was able to tweak your version a bit](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3f45f0c197fd976f46db4b032f24ab79) to get it to work with just `Display`, but like you said, having both would run into a conflict. While [doing it the second way](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5ad7142b259cfeaa3b0a71dd331f876a) gets away from the conflict, but still has the compile-time size issue. – 0b10011 Aug 23 '21 at 18:51
  • 1
    That's because `fmt::Display` is a trait, not a type. `impl From for Value` roughly means "for any type `T` that satisfies trait `fmt::Display`, `Value` satisfies the trait `From` (i.e. `T` can be convert into `Value`)". Since `fmt::Display` is not a type, it doesn't make sense to say `Value` satisfies the trait `From`. (Saying "`fmt::Display` can be converted into `Value`" doesn't make sense, but saying "any type that satisfies `fmt::Display` can be converted into `Value`" does. – Bernard Aug 24 '21 at 13:09
  • 1
    However, `impl From> for Value` does make sense, because `Box` is a type, not a trait. (It is a type that allows storage of any object that satisfies `fmt::Display`, at the cost of one indirection / heap allocation at runtime.) – Bernard Aug 24 '21 at 13:11

1 Answers1

0

As mentioned by @Bernard in the comments, the compile time size issue can be worked around with impl<T: 'static + fmt::Display> From<T> for Value, but it will result in conflicting implementations because of the generic type T. It may be able to adjust this code to work once specialization is merged.

Until then, if you're able to replace one of the traits with a type instead, it can work (playground):

use std::fmt;
use std::io::Read;

struct PrivateData(String);

enum Value {
    Display(Box<dyn fmt::Display>),
    PrivateDisplay(Box<PrivateData>),
}

impl Value {
    fn as_string(self) -> String {
        match self {
            Value::Display(display) => format!("{}", display),
            Value::PrivateDisplay(private) => format!("{}", private.0),
        }
    }
}

impl<T: 'static + fmt::Display> From<T> for Value {
    fn from(t: T) -> Self {
        Value::Display(Box::new(t))
    }
}

impl Into<Value> for PrivateData {
    fn into(self) -> Value {
        Value::PrivateDisplay(Box::new(self))
    }
}

struct Data {
    value: Value,
}

fn create_data<V>(value: V) -> Data
where
    V: Into<Value>
{
    Data {
        value: value.into(),
    }
}

fn main() {
    println!("{}", create_data("str").value.as_string());
    println!("{}", create_data(PrivateData(String::from("string"))).value.as_string());
    
    // May also be data provided from outside program
    let mut buffer = String::from("buffer");
    std::io::stdin().read_to_string(&mut buffer).unwrap();
    println!("{}", create_data(buffer).value.as_string());
}
0b10011
  • 18,397
  • 4
  • 65
  • 86
  • 1
    It's better to do [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=628b84d0f3aee7e03fdb66cf86ef6ae0) instead. Firstly, it's preferable to implement `From` instead of `Into` (see the [documentation](https://doc.rust-lang.org/std/convert/trait.From.html)). Secondly, we don't need a Box for Value::PrivateDisplay since PrivateData is a type (with known size) - the Box is necessary for Value::Display only because we want to be able to store _any_ type that implements fmt::Display (which means that at compile time we can't know the space that this object needs). – Bernard Aug 24 '21 at 13:18
  • @Bernard thanks so much for all of that explanation! – 0b10011 Aug 24 '21 at 15:41