0

Below is my non-compiling code to see my situation.

I want to create a logger with a type that implements Debug and Clone. This is because I first want to log the error (I think I need to clone the value) on the machine, and then send it to the database.

I want to initialize/use my type with either:

  • &str
  • String
  • Some type that implements Debug + Clone

Notes:

  • I don't want specialized methods for each of the above types. I have a lot of methods and I don't want to keep repeating the generics.
  • I don't want calling my type with a generic.
  • I don't care about some extra allocation, a.k.a using a Box for example.
  • I will never assign the result of an instance of Logger to a variable (maybe this enables the type to be unsized?), I just log a few things.

Trying to Box around the Debug and Clone requirement gives me this error...

only auto traits can be used as additional traits in a trait object

... when using something like this:

pub fn new<D: Clone + Debug>(con: &'a DBCon, err: D) -> Logger

Code

struct DBCon;

struct Logger<'a> {
    con: &'a DBCon,
    err: ?? // Some type implementing Debug + Clone
}

impl<'a> Logger<'a> {
    pub fn new(con: &'a DBCon, err: ??) -> Logger {
        Logger { con, err }
    }

    pub fn log(self) {
        eprintln!("{:?}", self.err.clone())

        // Send self.err to database
    }

    // Additional log methods
}

fn main() {
    Logger::new(con: some_con, "err").log();

    Logger::new(con: some_con, "err".to_owned()).log();

    Logger::new(con: some_con, some_other_value_that_impl_Debug_and_Clone).log();
}
J. Doe
  • 12,159
  • 9
  • 60
  • 114
  • Does this answer your question? [Can I get a trait object of a multi-trait instance without using a generic type?](https://stackoverflow.com/questions/28897297/can-i-get-a-trait-object-of-a-multi-trait-instance-without-using-a-generic-type) – SCappella Jan 09 '20 at 21:28
  • _I don't want calling my type with a generic_ - I am curious why having generic arguments is not acceptable in your case. – Raj Jan 09 '20 at 21:34
  • @Raj I want to make calling as easy as possible, without having to worry about the underlying type. I added some examples in the main method. Maybe it's not possible what I want, i don't know Rust very well. – J. Doe Jan 09 '20 at 21:39
  • @SCappella I am unable to call `new`, because the trait 'can not be made into an object' with the provided link. – J. Doe Jan 09 '20 at 21:43
  • @J.Doe I see. Yes, generic arguments can occasionally make using types/functions more complex. But for the vast majority of the cases, the compiler's type inference should make it quite seamless. Is your type definition somehow forcing callers specify types explicitly if you use generics? – Raj Jan 09 '20 at 21:47
  • Even when using a generic, you don't need to specify any types on use: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=eae872c5526b4809807bc609efe9b6cc). – Jmb Jan 10 '20 at 08:31
  • @Jmb Woa, it worked! What is the difference between `Self` and the type name (like I did in the playground example)? If you post your code as an answer and some background about using `Self`, I will accept the answer! – J. Doe Jan 10 '20 at 12:24
  • Note that the important point here isn't that I used `Self`, but that I replaced your `Logger<'a>` with a `Logger<'a, T: Clone + Debug>`. `Self` is just a shortcut for the full type name. – Jmb Jan 10 '20 at 13:16

1 Answers1

1

You can make your Logger generic without needing to specify the generic parameters at the call sites:

use std::fmt::Debug;

struct DBCon;

struct Logger<'a, T: Debug + Clone> {
    con: &'a DBCon,
    err: T,
}

impl<'a, T: Debug + Clone> Logger<'a, T> {
    pub fn new (con: &'a DBCon, err: T) -> Self {
        Logger { con, err }
    }

    pub fn log (self) {
        eprintln!("{:?}", self.err.clone())
        // Send self.err to database
    }
    // Additional log methods
}

fn main() {
    let con = DBCon;

    Logger::new (&con, "err").log();
    Logger::new (&con, "err".to_owned()).log();
    // Logger::new (&con, some_other_value_that_impl_Debug_and_Clone).log();
}

Playground

At the call sites (in the main function in the example), the compiler is able to guess the types automagically, so your users don't need to type them.

Note also that I used Self as the return type for the Logger::new method. This is equivalent to the full type that I am currently implementing, so in this case Self is an alias for Logger<'a, T>. I could as well have written: pub fn new (con: &'a DBCon, err: T) -> Logger<'a, T> but not pub fn new (con: &'a DBCon, err: T) -> Logger because Logger on its own is an incomplete type, lacking the generic parameters, and the compiler won't infer types in function prototypes.

Jmb
  • 18,893
  • 2
  • 28
  • 55